dukpt 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Shopify
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,36 @@
1
+ # DUKPT
2
+
3
+ This library implements a decrypter for ciphertext originating from a device using a Derived Unique Key Per Transaction (DUKPT) scheme.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'dukpt'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install dukpt
18
+
19
+ ## Usage
20
+
21
+ # Instantiate a decrypter with your base derivation key (BDK)
22
+ decrypter = DUKPT::Decrypter.new("0123456789ABCDEFFEDCBA9876543210")
23
+
24
+ # Pass the ciphertext and the current Key Serial Number (KSN), as hex encoded strings, to the decryptor to get back the plaintext
25
+ ksn = "FFFF9876543210E00008"
26
+ ciphertext = "C25C1D1197D31CAA87285D59A892047426D9182EC11353C051ADD6D0F072A6CB3436560B3071FC1FD11D9F7E74886742D9BEE0CFD1EA1064C213BB55278B2F12"
27
+
28
+ plaintext = decrypter.decrypt(ciphertext, ksn) # => "%B5452300551227189^HOGAN/PAUL ^08043210000000725000000?\x00\x00\x00\x00"
29
+
30
+ ## Contributing
31
+
32
+ 1. Fork it
33
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
34
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
35
+ 4. Push to the branch (`git push origin my-new-feature`)
36
+ 5. Create new Pull Request
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ require "rake/testtask"
4
+
5
+ task :default => :test
6
+
7
+ Rake::TestTask.new(:test) do |t|
8
+ t.pattern = 'test/**/*_test.rb'
9
+ t.ruby_opts << '-rubygems'
10
+ t.libs << 'test'
11
+ t.verbose = true
12
+ end
@@ -0,0 +1,17 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/dukpt/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["David Seal", "Cody Fauser"]
6
+ gem.email = ["david.seal@shopify.com", "cody@shopify.com"]
7
+ gem.description = %q{Implements a Derived Unique Key Per Transaction (DUKPT) decrypter}
8
+ gem.summary = %q{Implements a Derived Unique Key Per Transaction (DUKPT) decrypter}
9
+ gem.homepage = ""
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "dukpt"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = DUKPT::VERSION
17
+ end
@@ -0,0 +1,3 @@
1
+ require 'dukpt/encryption'
2
+ require 'dukpt/decrypter'
3
+ require "dukpt/version"
@@ -0,0 +1,17 @@
1
+ module DUKPT
2
+ class Decrypter
3
+ include Encryption
4
+
5
+ attr_reader :bdk
6
+ def initialize(bdk)
7
+ @bdk = bdk
8
+ end
9
+
10
+ def decrypt(cryptogram, ksn)
11
+ ipek = derive_IPEK(bdk, ksn)
12
+ pek = derive_PEK(ipek, ksn)
13
+ decrypted_cryptogram = triple_des_decrypt(pek, cryptogram)
14
+ [decrypted_cryptogram].pack('H*')
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,106 @@
1
+ require 'openssl'
2
+
3
+ module DUKPT
4
+ module Encryption
5
+ REG3_MASK = 0x1FFFFF
6
+ SHIFT_REG_MASK = 0x100000
7
+ REG8_MASK = 0xFFFFFFFFFFE00000
8
+ LS16_MASK = 0x0000000000000000FFFFFFFFFFFFFFFF
9
+ MS16_MASK = 0xFFFFFFFFFFFFFFFF0000000000000000
10
+ KEY_MASK = 0xC0C0C0C000000000C0C0C0C000000000
11
+ PEK_MASK = 0x00000000000000FF00000000000000FF
12
+ KSN_MASK = 0xFFFFFFFFFFFFFFE00000
13
+
14
+ def derive_key(ipek, ksn)
15
+ ksn_current = ksn.to_i(16)
16
+
17
+ # Get 8 least significant bytes
18
+ ksn_reg = ksn_current & LS16_MASK
19
+
20
+ # Clear the 21 counter bits
21
+ ksn_reg = ksn_reg & REG8_MASK
22
+
23
+ # Grab the 21 counter bits
24
+ reg_3 = ksn_current & REG3_MASK
25
+ shift_reg = SHIFT_REG_MASK
26
+
27
+ #Initialize "curkey" to be the derived "ipek"
28
+ curkey = ipek.to_i(16)
29
+ while (shift_reg > 0)
30
+ if shift_reg & reg_3 > 0
31
+ ksn_reg = shift_reg | ksn_reg
32
+ curkey = keygen(curkey, ksn_reg)
33
+ end
34
+ shift_reg = shift_reg >> 1
35
+ end
36
+ hex_string_from_val(curkey, 16)
37
+ end
38
+
39
+ def keygen(key, ksn)
40
+ cr1 = ksn
41
+ cr2 = encrypt_register(key, cr1)
42
+
43
+ key2 = key ^ KEY_MASK
44
+
45
+ cr1 = encrypt_register(key2, cr1)
46
+
47
+ [hex_string_from_val(cr1, 8), hex_string_from_val(cr2, 8)].join.to_i(16)
48
+ end
49
+
50
+ def pek_from_key(key)
51
+ hex_string_from_val((key.to_i(16) ^ PEK_MASK), 16)
52
+ end
53
+
54
+ def derive_PEK(ipek, ksn)
55
+ pek_from_key(derive_key(ipek, ksn))
56
+ end
57
+
58
+ def derive_IPEK(bdk, ksn)
59
+ ksn_cleared_count = (ksn.to_i(16) & KSN_MASK) >> 16
60
+ left_half_of_ipek = triple_des_encrypt(bdk, hex_string_from_val(ksn_cleared_count, 8))
61
+ xor_base_derivation_key = bdk.to_i(16) ^ KEY_MASK
62
+ right_half_of_ipek = triple_des_encrypt(hex_string_from_val(xor_base_derivation_key, 8), hex_string_from_val(ksn_cleared_count, 8))
63
+ ipek_derived = left_half_of_ipek + right_half_of_ipek
64
+ end
65
+
66
+ def triple_des_decrypt(key, message)
67
+ openssl_encrypt("des-ede-cbc", key, message, false)
68
+ end
69
+
70
+ def triple_des_encrypt(key, message)
71
+ openssl_encrypt("des-ede-cbc", key, message, true)
72
+ end
73
+
74
+ def des_encrypt(key, message)
75
+ openssl_encrypt("des-cbc", key, message, true)
76
+ end
77
+
78
+ private
79
+
80
+ def hex_string_from_val val, bytes
81
+ val.to_s(16).rjust(bytes * 2, "0")
82
+ end
83
+ def encrypt_register(curkey, reg_8)
84
+ left_key_half = (curkey & MS16_MASK) >> 64
85
+ right_key_half = curkey & LS16_MASK
86
+
87
+ message = right_key_half ^ reg_8
88
+ ciphertext = des_encrypt(hex_string_from_val(left_key_half, 8), hex_string_from_val(message, 8)).to_i(16)
89
+ result = right_key_half ^ ciphertext
90
+
91
+ result
92
+ end
93
+
94
+ def openssl_encrypt(cipher_type, key, message, is_encrypt)
95
+ cipher = OpenSSL::Cipher::Cipher::new(cipher_type)
96
+ is_encrypt ? cipher.encrypt : cipher.decrypt
97
+ cipher.padding = 0
98
+ cipher.key = [key].pack('H*')
99
+ # No Initial Vector is used in the process.
100
+ cipher_result = ""
101
+ cipher_result << cipher.update([message].pack('H*'))
102
+ cipher_result << cipher.final
103
+ cipher_result.unpack('H*')[0]
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,3 @@
1
+ module DUKPT
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,17 @@
1
+ require 'bundler/setup'
2
+ require 'test/unit'
3
+ require 'dukpt'
4
+
5
+ class DUKPT::DecrypterTest < Test::Unit::TestCase
6
+
7
+ def test_decrypt_track_data
8
+ bdk = "0123456789ABCDEFFEDCBA9876543210"
9
+ ksn = "FFFF9876543210E00008"
10
+ ciphertext = "C25C1D1197D31CAA87285D59A892047426D9182EC11353C051ADD6D0F072A6CB3436560B3071FC1FD11D9F7E74886742D9BEE0CFD1EA1064C213BB55278B2F12"
11
+ plaintext = "%B5452300551227189^HOGAN/PAUL ^08043210000000725000000?\x00\x00\x00\x00"
12
+
13
+ decrypter = DUKPT::Decrypter.new(bdk)
14
+ assert_equal plaintext, decrypter.decrypt(ciphertext, ksn)
15
+ end
16
+
17
+ end
@@ -0,0 +1,93 @@
1
+ require 'test/unit'
2
+ require 'bundler/setup'
3
+ require 'dukpt'
4
+
5
+ class DUKPT::EncryptionTest < Test::Unit::TestCase
6
+ include DUKPT::Encryption
7
+
8
+ def test_least_signinficant_16_nibbles_mask
9
+ expected = 0x00009876543210E00008
10
+ actual = 0xFFFF9876543210E00008 & LS16_MASK
11
+ assert_equal expected, actual
12
+ end
13
+
14
+ def test_register_8_mask
15
+ expected = 0x00009876543210e00000
16
+ actual = 0xFFFF9876543210E00008 & REG8_MASK
17
+ assert_equal expected, actual
18
+ end
19
+
20
+ def test_register_3_mask
21
+ expected = 0x1FFFFF
22
+ actual = 0xFFFFFFFFFFFFFFFFFFFF & REG3_MASK
23
+ assert_equal expected, actual
24
+ end
25
+
26
+ def test_derive_ipek
27
+ ksn = "FFFF9876543210E00008"
28
+ bdk = "0123456789ABCDEFFEDCBA9876543210"
29
+ ipek = derive_IPEK(bdk, ksn)
30
+ assert_equal '6ac292faa1315b4d858ab3a3d7d5933a', ipek
31
+ end
32
+
33
+ def test_derive_pek
34
+ ksn = "FFFF9876543210E00008"
35
+ pek = derive_PEK('6ac292faa1315b4d858ab3a3d7d5933a', ksn)
36
+ assert_equal '27f66d5244ff621eaa6f6120edeb427f', pek
37
+ end
38
+
39
+ def test_derive_key_3
40
+ ksn = "FFFF9876543210E00003"
41
+ key = derive_key('6ac292faa1315b4d858ab3a3d7d5933a', ksn)
42
+ assert_equal '0DF3D9422ACA56E547676D07AD6BADFA', key.upcase
43
+ end
44
+
45
+ def test_derive_pek_counter_3
46
+ ksn = "FFFF9876543210E00003"
47
+ pek = derive_PEK('6ac292faa1315b4d858ab3a3d7d5933a', ksn)
48
+ assert_equal '0DF3D9422ACA561A47676D07AD6BAD05', pek.upcase
49
+ end
50
+
51
+ def test_derive_pek_counter_7
52
+ ksn = "FFFF9876543210E00007"
53
+ pek = derive_PEK('6ac292faa1315b4d858ab3a3d7d5933a', ksn)
54
+ assert_equal '0C8F780B7C8B492FAE84A9EB2A6CE69F', pek.upcase
55
+ end
56
+
57
+ def test_derive_pek_counter_F
58
+ ksn = "FFFF9876543210E0000F"
59
+ pek = derive_PEK('6ac292faa1315b4d858ab3a3d7d5933a', ksn)
60
+ assert_equal '93DD5B956C4878B82E453AAEFD32A555', pek.upcase
61
+ end
62
+
63
+ def test_derive_pek_counter_10
64
+ ksn = "FFFF9876543210E00010"
65
+ pek = derive_PEK('6ac292faa1315b4d858ab3a3d7d5933a', ksn)
66
+ assert_equal '59598DCBD9BD943F94165CE453585FA8', pek.upcase
67
+ end
68
+
69
+ def test_derive_pek_counter_13
70
+ ksn = "FFFF9876543210E00013"
71
+ pek = derive_PEK('6ac292faa1315b4d858ab3a3d7d5933a', ksn)
72
+ assert_equal 'C3DF489FDF11534BF03DE97C27DC4CD0', pek.upcase
73
+ end
74
+
75
+ def test_derive_pek_counter_EFF800
76
+ ksn = "FFFF9876543210EFF800"
77
+ pek = derive_PEK('6ac292faa1315b4d858ab3a3d7d5933a', ksn)
78
+ assert_equal 'F9CDFEBF4F5B1D61B3EC12454527E189', pek.upcase
79
+ end
80
+
81
+ def test_triple_des_decrypt
82
+ ciphertext = "C25C1D1197D31CAA87285D59A892047426D9182EC11353C051ADD6D0F072A6CB3436560B3071FC1FD11D9F7E74886742D9BEE0CFD1EA1064C213BB55278B2F12"
83
+ data_decrypted = triple_des_decrypt('27f66d5244ff621eaa6f6120edeb427f', ciphertext)
84
+ assert_equal '2542353435323330303535313232373138395e484f47414e2f5041554c2020202020205e30383034333231303030303030303732353030303030303f00000000', data_decrypted
85
+ end
86
+
87
+ def test_unpacking_decrypted_data
88
+ data_decrypted = '2542353435323330303535313232373138395e484f47414e2f5041554c2020202020205e30383034333231303030303030303732353030303030303f00000000'
89
+ expected = "%B5452300551227189^HOGAN/PAUL ^08043210000000725000000?\x00\x00\x00\x00"
90
+ assert_equal expected, [data_decrypted].pack('H*')
91
+ end
92
+
93
+ end
metadata ADDED
@@ -0,0 +1,61 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dukpt
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - David Seal
9
+ - Cody Fauser
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2013-03-18 00:00:00.000000000 Z
14
+ dependencies: []
15
+ description: Implements a Derived Unique Key Per Transaction (DUKPT) decrypter
16
+ email:
17
+ - david.seal@shopify.com
18
+ - cody@shopify.com
19
+ executables: []
20
+ extensions: []
21
+ extra_rdoc_files: []
22
+ files:
23
+ - .gitignore
24
+ - Gemfile
25
+ - LICENSE
26
+ - README.md
27
+ - Rakefile
28
+ - dupkt.gemspec
29
+ - lib/dukpt.rb
30
+ - lib/dukpt/decrypter.rb
31
+ - lib/dukpt/encryption.rb
32
+ - lib/dukpt/version.rb
33
+ - test/decrypter_test.rb
34
+ - test/encryption_test.rb
35
+ homepage: ''
36
+ licenses: []
37
+ post_install_message:
38
+ rdoc_options: []
39
+ require_paths:
40
+ - lib
41
+ required_ruby_version: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ required_rubygems_version: !ruby/object:Gem::Requirement
48
+ none: false
49
+ requirements:
50
+ - - ! '>='
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ requirements: []
54
+ rubyforge_project:
55
+ rubygems_version: 1.8.25
56
+ signing_key:
57
+ specification_version: 3
58
+ summary: Implements a Derived Unique Key Per Transaction (DUKPT) decrypter
59
+ test_files:
60
+ - test/decrypter_test.rb
61
+ - test/encryption_test.rb