dukpt 0.0.1

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,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