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.
- checksums.yaml +7 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +21 -0
- data/README.md +40 -0
- data/Rakefile +13 -0
- data/aes256gcm_decrypt.gemspec +17 -0
- data/circle.yml +8 -0
- data/ext/aes256gcm_decrypt/aes256gcm_decrypt.c +136 -0
- data/ext/aes256gcm_decrypt/extconf.rb +6 -0
- data/spec/decrypt_spec.rb +60 -0
- data/spec/key_hex.txt +1 -0
- data/spec/token_data_base64.txt +1 -0
- metadata +83 -0
checksums.yaml
ADDED
@@ -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
data/LICENSE.txt
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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)
|
data/Rakefile
ADDED
@@ -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
|
data/circle.yml
ADDED
@@ -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,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
|
data/spec/key_hex.txt
ADDED
@@ -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: []
|