aes256gcm_decrypt 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0cd53e441e4de2f878cacf26642c881f10784dee
4
+ data.tar.gz: af758a6f954de18d0456030d8c3d18529338e2d2
5
+ SHA512:
6
+ metadata.gz: 43380846a99f752fdf9ad4e81847ed2f4bd43c2336b81d938e8415e1d9e46987d131fdcdd29d1e5b368323ee19206faf0bbb0e267a3482a0db326e8d32217edb
7
+ data.tar.gz: 73ed2b8634cbd6c9f74e8b23098f10488c673b7173cec5d7c4d06bc38d987b8c70978c2875f2fca029d73e6918e04b02295d88b6a22cb6f10667ba5c341af469
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 Clearhaus
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,40 @@
1
+ # Aes256GcmDecrypt
2
+
3
+ Decrypt AES256GCM-encrypted data in Apple Pay Payment Tokens.
4
+
5
+ This library is necessary for Ruby < 2.4 (if you use the stdlib openssl rather than the [openssl gem](https://rubygems.org/gems/openssl)), as the OpenSSL bindings do not support setting the length of the initialisation vector (IV). Setting the IV length is necessary for decrypting Apple Pay data.
6
+
7
+ The library becomes obsolete when we start using Ruby >= 2.4.
8
+
9
+ ## Usage
10
+
11
+ ```
12
+ bundle install
13
+ bundle exec rake test
14
+
15
+ irb -r base64 -I lib -r aes256gcm_decrypt
16
+
17
+ ciphertext_and_tag = Base64.decode64(File.read('spec/token_data_base64.txt'))
18
+ key = [File.read('spec/key_hex.txt').strip].pack('H*')
19
+
20
+ begin
21
+ puts Aes256GcmDecrypt::decrypt(ciphertext_and_tag, key)
22
+ rescue Aes256GcmDecrypt::AuthenticationError => e
23
+ # somebody is up to something
24
+ rescue Aes256GcmDecrypt::Error => e
25
+ # super class for the possible errors; Aes256GcmDecrypt::InputError and
26
+ # Aes256GcmDecrypt::OpenSSLError are left, i.e. either you supplied invalid
27
+ # input or we got an unexpected error from OpenSSL
28
+ end
29
+ ```
30
+
31
+ See also [the specs](spec/decrypt_spec.rb).
32
+
33
+ ## Inspirational sources
34
+
35
+ * [Your first Ruby native extension: C](https://blog.jcoglan.com/2012/07/29/your-first-ruby-native-extension-c/)
36
+ * [Step-by-Step Guide to Building Your First Ruby Gem](https://quickleft.com/blog/engineering-lunch-series-step-by-step-guide-to-building-your-first-ruby-gem/)
37
+ * [OpenSSL EVP Authenticated Decryption using GCM](https://wiki.openssl.org/index.php/EVP_Authenticated_Encryption_and_Decryption#Authenticated_Decryption_using_GCM_mode)
38
+ * [The Ruby C API](http://silverhammermba.github.io/emberb/c/)
39
+ * [Apple Pay Payment Token Format Reference](https://developer.apple.com/library/content/documentation/PassKit/Reference/PaymentTokenJSON/PaymentTokenJSON.html)
40
+ * [Spreedly's gala gem](https://github.com/spreedly/gala)
@@ -0,0 +1,13 @@
1
+ require 'rake/extensiontask'
2
+ require 'rspec/core/rake_task'
3
+
4
+ spec = Gem::Specification.load('aes256gcm_decrypt.gemspec')
5
+ Rake::ExtensionTask.new('aes256gcm_decrypt', spec)
6
+
7
+ desc ''
8
+ RSpec::Core::RakeTask.new(:spec) do |task|
9
+ task.pattern = './spec/**/*_spec.rb'
10
+ end
11
+
12
+ desc 'Compile extension and run specs'
13
+ task :test => [:compile, :spec]
@@ -0,0 +1,17 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'aes256gcm_decrypt'
3
+ s.version = '0.0.2'
4
+ s.summary = 'Decrypt AES256GCM-encrypted data in Apple Pay Payment Tokens'
5
+ s.author = 'Clearhaus'
6
+ s.homepage = 'https://github.com/clearhaus/aes256gcm_decrypt'
7
+ s.license = 'MIT'
8
+
9
+ s.files = `git ls-files -z`.split("\x0")
10
+
11
+ s.extensions << "ext/aes256gcm_decrypt/extconf.rb"
12
+
13
+ s.required_ruby_version = '< 2.4'
14
+
15
+ s.add_development_dependency "rake-compiler"
16
+ s.add_development_dependency "rspec"
17
+ end
@@ -0,0 +1,8 @@
1
+ ---
2
+ machine:
3
+ ruby:
4
+ version: 2.3.3
5
+
6
+ test:
7
+ override:
8
+ - bundle exec rake test
@@ -0,0 +1,136 @@
1
+ #include <ruby.h>
2
+ #include <stdio.h>
3
+ #include <stdlib.h>
4
+ #include <string.h>
5
+ #include <openssl/evp.h>
6
+
7
+ VALUE aes256gcm_input_error;
8
+ VALUE aes256gcm_openssl_error;
9
+ VALUE aes256gcm_auth_error;
10
+
11
+ VALUE aes256gcm_decrypt(VALUE self, VALUE rb_ciphertext_and_tag, VALUE rb_key) {
12
+
13
+ /* Declare variables */
14
+ VALUE result;
15
+ unsigned int ciphertext_and_tag_len, ciphertext_len, tag_len;
16
+ unsigned int block_len, key_len, iv_len;
17
+ int plaintext_len, len;
18
+ unsigned char *ciphertext, *tag, *key, *plaintext;
19
+ char *rb_ciphertext_p;
20
+ unsigned char iv[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
21
+ const char *openssl_error_message = "";
22
+ EVP_CIPHER_CTX *ctx;
23
+
24
+ /* Check parameters */
25
+ if (!RB_TYPE_P(rb_ciphertext_and_tag, T_STRING)) {
26
+ rb_raise(aes256gcm_input_error, "ciphertext_and_tag must be a string");
27
+ }
28
+ if (!RB_TYPE_P(rb_key, T_STRING)) {
29
+ rb_raise(aes256gcm_input_error, "key must be a string");
30
+ }
31
+ ciphertext_and_tag_len = RSTRING_LEN(rb_ciphertext_and_tag);
32
+ tag_len = 16;
33
+ if (ciphertext_and_tag_len <= tag_len) {
34
+ rb_raise(aes256gcm_input_error, "ciphertext_and_tag too short");
35
+ }
36
+ key_len = RSTRING_LEN(rb_key);
37
+ if (key_len != 32) {
38
+ rb_raise(aes256gcm_input_error, "length of key must be 32");
39
+ }
40
+
41
+ /* Prepare variables */
42
+ result = Qnil;
43
+ block_len = 16;
44
+ iv_len = 16;
45
+
46
+ ciphertext_len = ciphertext_and_tag_len - tag_len;
47
+ ciphertext = calloc(ciphertext_len, sizeof(unsigned char));
48
+ rb_ciphertext_p = StringValuePtr(rb_ciphertext_and_tag);
49
+ memcpy(ciphertext, rb_ciphertext_p, ciphertext_len);
50
+
51
+ tag = calloc(tag_len, sizeof(unsigned char));
52
+ memcpy(tag, &rb_ciphertext_p[ciphertext_len], tag_len);
53
+
54
+ key = calloc(key_len, sizeof(unsigned char));
55
+ memcpy(key, StringValuePtr(rb_key), key_len);
56
+
57
+ plaintext = calloc(ciphertext_len + block_len, sizeof(unsigned char));
58
+
59
+ /* Create and initialise context */
60
+ if (!(ctx = EVP_CIPHER_CTX_new())) {
61
+ openssl_error_message = "Could not create and initialise context";
62
+ goto cleanup1;
63
+ }
64
+
65
+ /* Initialise decryption operation */
66
+ if (!EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL)) {
67
+ openssl_error_message = "Could not initialise decryption operation";
68
+ goto cleanup2;
69
+ }
70
+
71
+ /* Set initialisation vector (IV) length */
72
+ if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, iv_len, NULL)) {
73
+ openssl_error_message = "Could not set IV length";
74
+ goto cleanup2;
75
+ }
76
+
77
+ /* Initialise key and IV */
78
+ if (!EVP_DecryptInit_ex(ctx, NULL, NULL, key, iv)) {
79
+ openssl_error_message = "Could not initialise key and IV";
80
+ goto cleanup2;
81
+ }
82
+
83
+ /* Provide message to be decrypted */
84
+ if (!EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len)) {
85
+ openssl_error_message = "DecryptUpdate failed";
86
+ goto cleanup2;
87
+ }
88
+ plaintext_len = len;
89
+
90
+ /* Set expected tag */
91
+ if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, tag_len, tag)) {
92
+ openssl_error_message = "Could not set expected tag";
93
+ goto cleanup2;
94
+ }
95
+
96
+ /* Finalise decryption */
97
+ if (EVP_DecryptFinal_ex(ctx, &plaintext[len], &len) > 0) {
98
+ plaintext_len += len;
99
+ if ((unsigned int)plaintext_len > ciphertext_len + block_len) {
100
+ fprintf(stderr, "Plaintext overflow in AES256GCM decryption! Aborting.\n");
101
+ abort();
102
+ }
103
+ result = rb_str_new((char *)plaintext, plaintext_len);
104
+ } else {
105
+ result = Qfalse;
106
+ }
107
+
108
+ cleanup2:
109
+ EVP_CIPHER_CTX_free(ctx);
110
+
111
+ cleanup1:
112
+ free(plaintext);
113
+ free(key);
114
+ free(tag);
115
+ free(ciphertext);
116
+
117
+ switch (result) {
118
+ case Qnil:
119
+ rb_raise(aes256gcm_openssl_error, "%s", openssl_error_message);
120
+ case Qfalse:
121
+ rb_raise(aes256gcm_auth_error, "Authentication failed");
122
+ default:
123
+ return result;
124
+ }
125
+ }
126
+
127
+ void Init_aes256gcm_decrypt() {
128
+ VALUE module, error;
129
+
130
+ module = rb_define_module("Aes256GcmDecrypt");
131
+ error = rb_define_class_under(module, "Error", rb_eStandardError);
132
+ aes256gcm_input_error = rb_define_class_under(module, "InputError", error);
133
+ aes256gcm_openssl_error = rb_define_class_under(module, "OpenSSLError", error);
134
+ aes256gcm_auth_error = rb_define_class_under(module, "AuthenticationError", error);
135
+ rb_define_singleton_method(module, "decrypt", aes256gcm_decrypt, 2);
136
+ }
@@ -0,0 +1,6 @@
1
+ require 'mkmf'
2
+ extension_name = 'aes256gcm_decrypt'
3
+ dir_config(extension_name)
4
+ $CFLAGS << ' -Wall'
5
+ $LDFLAGS << ' -lcrypto'
6
+ create_makefile(extension_name)
@@ -0,0 +1,60 @@
1
+ require 'aes256gcm_decrypt'
2
+ require 'base64'
3
+
4
+ describe 'Aes256GcmDecrypt::decrypt' do
5
+ before(:each) do
6
+ @ciphertext_and_tag = Base64.decode64(File.read('spec/token_data_base64.txt'))
7
+ @key = [File.read('spec/key_hex.txt').strip].pack('H*')
8
+ end
9
+
10
+ it 'decrypts correctly' do
11
+ plaintext = Aes256GcmDecrypt::decrypt(@ciphertext_and_tag, @key)
12
+ expect(plaintext).to eq \
13
+ '{"applicationPrimaryAccountNumber":"4109370251004320","applicationExp' +
14
+ 'irationDate":"200731","currencyCode":"840","transactionAmount":100,"d' +
15
+ 'eviceManufacturerIdentifier":"040010030273","paymentDataType":"3DSecu' +
16
+ 're","paymentData":{"onlinePaymentCryptogram":"Af9x/QwAA/DjmU65oyc1MAA' +
17
+ 'BAAA=","eciIndicator":"5"}}'
18
+ end
19
+
20
+ it 'detects wrong parameter types' do
21
+ expect{Aes256GcmDecrypt::decrypt(42, @key)}.to \
22
+ raise_error(Aes256GcmDecrypt::InputError, 'ciphertext_and_tag must be a string')
23
+ expect{Aes256GcmDecrypt::decrypt(@ciphertext_and_tag, 42)}.to \
24
+ raise_error(Aes256GcmDecrypt::InputError, 'key must be a string')
25
+ end
26
+
27
+ it 'detects too short ciphertext_and_tag' do
28
+ (0..16).each do |i|
29
+ ciphertext_and_tag = 'x' * i
30
+ expect{Aes256GcmDecrypt::decrypt(ciphertext_and_tag, @key)}.to \
31
+ raise_error(Aes256GcmDecrypt::InputError, 'ciphertext_and_tag too short')
32
+ end
33
+ end
34
+
35
+ it 'detects wrong key length' do
36
+ ((0..64).to_a - [32]).each do |i|
37
+ key = 'x' * i
38
+ expect{Aes256GcmDecrypt::decrypt(@ciphertext_and_tag, key)}.to \
39
+ raise_error(Aes256GcmDecrypt::InputError, 'length of key must be 32')
40
+ end
41
+ end
42
+
43
+ it 'detects tampering with the ciphertext' do
44
+ @ciphertext_and_tag[0] = 'x'
45
+ expect{Aes256GcmDecrypt::decrypt(@ciphertext_and_tag, @key)}.to \
46
+ raise_error(Aes256GcmDecrypt::AuthenticationError, 'Authentication failed')
47
+ end
48
+
49
+ it 'detects an incorrect tag' do
50
+ @ciphertext_and_tag[-1] = 'x'
51
+ expect{Aes256GcmDecrypt::decrypt(@ciphertext_and_tag, @key)}.to \
52
+ raise_error(Aes256GcmDecrypt::AuthenticationError, 'Authentication failed')
53
+ end
54
+
55
+ it 'detects an incorrect key' do
56
+ @key[0] = 'x'
57
+ expect{Aes256GcmDecrypt::decrypt(@ciphertext_and_tag, @key)}.to \
58
+ raise_error(Aes256GcmDecrypt::AuthenticationError, 'Authentication failed')
59
+ end
60
+ end
@@ -0,0 +1 @@
1
+ 1ce49a828f59d43861ba442fce6829b8218fbb0ab55b40206ac31058d66f5086
@@ -0,0 +1 @@
1
+ 4OZho15e9Yp5K0EtKergKzeRpPAjnKHwmSNnagxhjwhKQ5d29sfTXjdbh1CtTJ4DYjsD6kfulNUnYmBTsruphBz7RRVI1WI8P0LrmfTnImjcq1mi+BRN7EtR2y6MkDmAr78anff91hlc+x8eWD/NpO/oZ1ey5qV5RBy/Jp5zh6ndVUVq8MHHhvQv4pLy5Tfi57Yo4RUhAsyXyTh4x/p1360BZmoWomK15NcJfUmoUCuwEYoi7xUkRwNr1z4MKnzMfneSRpUgdc0wADMeB6u1jcuwqQnnh2cusiagOTCfD6jO6tmouvu6KO54uU7bAbKz6cocIOEAOc6keyFXG5dfw8i3hJg6G2vIefHCwcKu1zFCHr4P7jLnYFDEhvxLm1KskDcuZeQHAkBMmLRSgj9NIcpBa94VN/JTga8W75IWAA==
metadata ADDED
@@ -0,0 +1,83 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: aes256gcm_decrypt
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Clearhaus
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-01-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake-compiler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description:
42
+ email:
43
+ executables: []
44
+ extensions:
45
+ - ext/aes256gcm_decrypt/extconf.rb
46
+ extra_rdoc_files: []
47
+ files:
48
+ - Gemfile
49
+ - LICENSE.txt
50
+ - README.md
51
+ - Rakefile
52
+ - aes256gcm_decrypt.gemspec
53
+ - circle.yml
54
+ - ext/aes256gcm_decrypt/aes256gcm_decrypt.c
55
+ - ext/aes256gcm_decrypt/extconf.rb
56
+ - spec/decrypt_spec.rb
57
+ - spec/key_hex.txt
58
+ - spec/token_data_base64.txt
59
+ homepage: https://github.com/clearhaus/aes256gcm_decrypt
60
+ licenses:
61
+ - MIT
62
+ metadata: {}
63
+ post_install_message:
64
+ rdoc_options: []
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - "<"
70
+ - !ruby/object:Gem::Version
71
+ version: '2.4'
72
+ required_rubygems_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ requirements: []
78
+ rubyforge_project:
79
+ rubygems_version: 2.5.2.1
80
+ signing_key:
81
+ specification_version: 4
82
+ summary: Decrypt AES256GCM-encrypted data in Apple Pay Payment Tokens
83
+ test_files: []