aes256gcm_decrypt 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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: []