如何使用DUKPT加密在读卡器中生成密文?

8

For

`BDK = "0123456789ABCDEFFEDCBA9876543210"` `KSN = "FFFF9876543210E00008"` 

生成的密文如下:
"C25C1D1197D31CAA87285D59A892047426D9182EC11353C051ADD6D0F072A6CB3436560B3071FC1FD11D9F7E74886742D9BEE0CFD1EA1064C213BB55278B2F12"`

我在这里找到了链接,我知道这个密文是基于BDK和KSN的,但是这128位长度的密文是如何生成的?它所涉及的步骤或算法是什么?有人可以简单地解释一下吗?我在谷歌搜索时发现很难理解相关文件。


你知道读卡器使用的BDK和KSN吗? - PaulG
你的问题具体是什么?如果你想知道DUKPT如何工作,那么你肯定可以查看相关规范文件。你能否更具体地说明你需要什么,而不是得到一个“RTFM”的回答呢? - Duncan Jones
嗨@PaulG,@DuncanJones,是的,我知道我的卡读取器中使用了BDK和KSN。但我对这个项目还很陌生。我完全不知道数据是如何在卡读取器中加密以及刷卡时生成结果密文的。是的,他们使用了DUKPT技术。 - Dolo
@PaulG,@DuncanJones我已经编辑了问题,你们可以提供任何链接或者解释其步骤吗? - Dolo
4个回答

16

关于DUKPT,在Wiki上有一些解释。如果那不够让你满意,这里有一些简要的解释。

引用http://www.maravis.com/library/derived-unique-key-per-transaction-dukpt/

什么是DUKPT? 每个交易都使用一次性加密密钥,这些密钥是从一个秘密主密钥派生的,并由加密和解密数据的实体(或设备)共享。 为什么需要DUKPT? 任何加密算法的安全性都取决于其密钥。如果用于使用该算法加密数据的密钥不安全,则最强大的算法也是无用的。这就像用最大和最强壮的锁锁定门,但如果你把钥匙藏在门垫下面,那么锁本身就没有用了。当我们谈论加密时,我们还需要记住要在另一端解密数据。 通常,任何加密方案中最薄弱的环节是加密和解密各方之间密钥的共享。 DUKPT试图确保两个方都可以加密和解密数据,而无需传递加密/解密密钥。 VISA发布的密码最佳实践文档还建议使用DUKPT以符合PCI DSS合规要求。
DUKPT如何工作? DUKPT使用每个交易生成并丢弃的一次性密钥。优点在于,如果其中一个密钥被破坏,只有一个交易会被破坏。对于DUKPT,发起方(例如Pin输入设备或PED)和接收方(处理器,网关等)共享一个密钥。实际上,该密钥不用于加密。相反,从此主密钥派生的另一个一次性密钥用于加密和解密数据。重要的是要注意,主密钥不应从派生的一次性密钥中恢复出来。 要解密数据,接收端必须知道用于生成一次性密钥的主密钥是哪个。这意味着接收端必须为每个设备存储和跟踪一个主密钥。对于支持大量设备的人来说,这可能是很多工作。需要更好的方法来处理此问题。 在现实生活中,接收方拥有一个称为基本导出密钥(BDK)的主密钥。BDK应保密且永远不会与任何人共享。该密钥用于生成称为初始Pin加密密钥(IPEK)的密钥。从这个密钥生成了一组称为Future Keys的密钥,并且IPEK被丢弃。 PED制造商将每个Future key嵌入到PED中,其中这些密钥是共享的。这个额外的派生步骤意味着接收方不必跟踪进入PED的每个密钥。当需要时,它们可以重新生成。
加密和解密 当需要从PED发送数据到接收器时,该设备中的Future key用于生成一次性密钥,然后将此密钥与加密算法一起用于加密数据。然后将此数据发送到接收方,以及由设备ID和设备事务计数器组成的密钥序列号(KSN)。 基于KSN,接收方然后生成IPEK,从中生成设备使用的Future Key,然后生成用于加密数据的实际密钥。使用此密钥,接收方将能够解密数据。

源代码


5

首先,让我引用你链接的完整源代码,你只提供了其中的3行...

require 'bundler/setup'
require 'test/unit'
require 'dukpt'

class DUKPT::DecrypterTest < Test::Unit::TestCase

      def test_decrypt_track_data
        bdk = "0123456789ABCDEFFEDCBA9876543210"
        ksn = "FFFF9876543210E00008"
        ciphertext = "C25C1D1197D31CAA87285D59A892047426D9182EC11353C051ADD6D0F072A6CB3436560B3071FC1FD11D9F7E74886742D9BEE0CFD1EA1064C213BB55278B2F12"
        plaintext = "%B5452300551227189^HOGAN/PAUL ^08043210000000725000000?\x00\x00\x00\x00"

        decrypter = DUKPT::Decrypter.new(bdk, "cbc")
        assert_equal plaintext, decrypter.decrypt(ciphertext, ksn)
      end
end

现在,您问的是“密文”是如何创建的... 首先我们知道它基于“明文”,该明文用于代码中验证解密是否有效。明文是0填充的-这符合正在通过使用DecrypterTest TestCase验证解密的加密。接下来看编码代码... 我在https://github.com/Shopify/dukpt/blob/master/lib/dukpt/encryption.rb找到了相关的加密代码。由于DecrypterTEst使用“cbc”,因此很明显加密使用:
 @cipher_type_des = "des-cbc"
 @cipher_type_tdes = "des-ede-cbc"

在加密代码的下面,以下内容解决了我们寻找答案的问题:
ciphertext = des_encrypt(...

这表明我们确实正在查看DES加密的结果。
现在,DES的块大小为64位。这是8个字节的二进制数,或者 - 由于“密文”是字节的十六进制编码文本表示形式 - 是16个字符的十六进制数。
“密文”有128个十六进制字符长,这意味着它包含(128个十六进制字符/ 16个十六进制字符=)8个DES块,每个块包含64位的加密信息。
简单回答:
当查看“密文”时,您正在查看(8个块的)DES加密数据,该数据使用人类可读的十六进制(2个十六进制字符= 1个字节)表示法代替原始二进制字节。
关于“重新创建”密文所涉及的步骤,我建议你简单地使用与你提问相关的ruby项目的相关部分。只需要查看源代码。位于"https://github.com/Shopify/dukpt/blob/master/lib/dukpt/encryption.rb"的文件几乎解释了所有内容,我相信你需要的所有功能都可以在该项目的GitHub存储库中找到。或者,您可以尝试使用自己选择的首选编程语言进行重新创建。您只需要处理两件事:DES加密/解密和bin-to-hex/hex-to-bin转换。

2
由于这是与此相关的首个主题之一,我想分享一下我如何对密文进行编码。这是我第一次使用Ruby,而且是专门为了使用DUKPT而使用它。
首先,我必须获取ipek和pek(与解密中相同)方法。然后将纯文本字符串解包。将解包的字符串转换为一个72字节的数组(如果我的术语不正确,请原谅我)。
我注意到在dukpt gem作者的示例中,他使用了以下纯文本字符串
"%B5452300551227189^HOGAN/PAUL ^08043210000000725000000?\x00\x00\x00\x00"
我认为该字符串不正确,因为名字后面不应该有空格(据我所知)...所以应该是
"%B5452300551227189^HOGAN/PAUL^08043210000000725000000?\x00\x00\x00\x00"
总的来说,这就是我最终选择的解决方案,它可以加密一个字符串,然后使用DUKPT解密它。
class Encrypt
include DUKPT::Encryption
attr_reader :bdk

def initialize(bdk, mode=nil)
  @bdk = bdk
  self.cipher_mode = mode.nil? ? 'cbc' : mode
end

def encrypt(plaintext, ksn)
  ipek = derive_IPEK(bdk, ksn)
  pek = derive_PEK(ipek, ksn)
  message =  plaintext.unpack("H*").first
  message = hex_string_from_unpacked(message, 72)
  encrypted_cryptogram = triple_des_encrypt(pek,message).upcase
  encrypted_cryptogram
end
def hex_string_from_unpacked val, bytes
  val.ljust(bytes * 2, "0")
end

结束

boomedukpt FFFF9876543210E00008 "%B5452300551227189^HOGAN/PAUL^08043210000000725000000?"

(我的Ruby宝石,KSN和纯文本字符串)

2542353435323330303535313232373138395e484f47414e2f5041554c5e30383034333231303030303030303732353030303030303f000000000000000000000000000000000000

(我的Ruby宝石调用hex_string_from_unpacked后在未打包的字符串上进行puts)

C25C1D1197D31CAA87285D59A892047426D9182EC11353C0B82D407291CED53DA14FB107DC0AAB9974DB6E5943735BFFE7D72062708FB389E65A38C444432A6421B7F7EDD559AF11

(我的Ruby宝石在加密字符串上进行puts)

%B5452300551227189^HOGAN/PAUL^08043210000000725000000?

(我的Ruby宝石在调用dukpt gem解密后进行puts)


0
看这个:https://github.com/sgbj/Dukpt.NET,我曾经遇到过类似的情况,当终端有自己的函数调用时,需要使用INIT和KSN来创建第一个密钥,所以我的唯一问题是确保在终端上生成的INIT密钥与上述存储库中的代码相同,这很容易使用ossl加密库进行3des加密并应用适当的掩码。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接