java - 使用 Visa Checkout 解密支付数据

标签 java encryption coldfusion coldfusion-11

我正在从 Visa Checkout 获取信息以加密格式。他们网站上的指南提供了这些说明:

First, you must decrypt the dynamic key (encKey), then use the decrypted dynamic key value to decrypt the payment data payload (encPaymentData).

Follow these four steps to decrypt the encKey:

  1. Base64-decode the encKey.
  2. Remove the first 32 bytes of the decoded value. This is the HMAC (Hash Message Authentication Code). Calculate a SHA-256 HMAC of the
    rest of the decoded data using your API Shared Secret and compare it
    to the HMAC from the first 32 bytes.
  3. The next 16 bytes should be removed and used as the IV (Initialization Vector) for the decryption algorithm.
  4. Decrypt the remaining data using AES-256-CBC, the IV from step 3, and the SHA-256 hash of your API Shared Secret.

Follow these four steps to decrypt the encPaymentData using the decrypted encKey:

  1. Base64-decode the encPaymentData.
  2. Remove the first 32 bytes of the decoded value. This is the HMAC. Calculate a SHA-256 HMAC of the rest of the decoded data using the decrypted encKey and compare it with the HMAC from the first 32 bytes.
  3. The next 16 bytes should be removed and used as the IV for the decryption algorithm.
  4. Decrypt the rest of the encPaymentData payload using AES-256-CBC, the IV from step 3, and the SHA256-hash of the decrypted encKey.

我尝试使用 ColdFusion,但我对加密问题有些迷惑,无法修复代码。下面我有什么需要的。我卡在第 3 步和第 4 步,他们说比较然后解密。有人可以指导如何修复它吗?

加密 key :

2M2WWOD4wANsGwWTmPqQIQYdz9WPwgiR0ntJHGaxm23b5a1sWUtCBcUQUMMtc9hvcYavJ6yqPgETOGZgDOdd9qjDwIb2aV9DLZT1iIcB3zNN5Ddhxd9iiui6TAlJxU/O

encPaymentData:

X2TXp0ZmwHrtfzSP5TPjUOjdZb0rjsHeDSqr8TwIF/VR8sMQhWN5hP4IRhQxWT CZcQhxZoUHP 0g/E/ot sjREAJ8YQf7c7jSzKsXRH/wrew5rQit2wJBlVSSZ YoLeIHbLrTz CfIoFv09hixl7ff27u0YCyV0zjP5vNfCBEIwfqyriqwXK2J QEOxQiKzDUW4br3o1t31aymCQC9eBBpoVKjFfSKlNXM9QEdNZBcLMZ8Wlv8lF/ua bnwshbM9u7Uhudqvut94RZEW NzkRD8MfBo12e/XhnL35qxGpHeQNPClC4EQDK6U/HmegeOj BZLbIIYBs6t9E8Q3AKBwfiPOFgB gSVnhXKnd3nKvllaG BaGrQJtk 7QAtnHMHxQAO5rdiS9465HCdiHa8zlv7SkvWh8EwcKCiT4qiZSM6QuYAeRSzDpPS1gsZ54Q9LizUnueH7yyzSd47cLPd0VlOQxobKtNN2LrsRb3IwOfzuwGnSRf2cNp49hBmmGP1b0BC hhB6UpCqP2ixTPvui NwMYzqZUe336bF1mfnKzEbEZIvIrPyx3uMiLDAns2g7S80gMNnHb/09i49xbfY3V7oudeiHV99FCh67DuG3uHE3/HzIZbcnxJwVJoJj6/3DuzK/Kw1JqSorE0M1qxUqoNkJC4aNCBrqfTlR7/eErrvB554TUZwcyQXqKCwrKv4NJEw6S0n3W1VASkfA0atbJQX2aLgx9kqnhYdDbaU8UcFIoeA45 yEuQ9vXzo2ILQhvamsAAFQd3i4mEOZ KNtMu25dDFlORn5C/oTZ1t1dzJoYMvq44gejp6L3IK e7JCugGchr963a2kd8NFa3wctRDHF8ChHxawVlU0aY7nasrVireMFLiM 9XIb4abfDtct/j1Q8IGN0hRkgCHO6dlnOrAaaQDYYH3axaMDp5Onb04whULYqGbn/XSR8Sn8gnoFbYqVJbR5aCp5Pe9TpfwxlEvV3z8ZfsASqW2y So9gpwg2y16K/FX3Io6kqeqUlAxbTRDfN/ofDIJaO H PUu2teqjvwvCkjPciGOQdXT5JxqoCiHqRwD0zeZPcG3b9Nfrq3Caf6zjwhf /CMeGc3dNHhSkXox R50MP8BlLWk/bXZRScTE/HSrVxE n073utHAnbVOM3gVha0Hr8TmoV8z1vBg5fY253so6kQX61ZIfHneCAZo0qeKRgDgLUryVPmUNc5 yKP8DxtmHl/0YUztpgyEx5njsrn1L 3EHMMUhui8d LQdNZoEpZ9U1Xb7XVsV5gnwR/mOITNOKJQsine4zMMHBcomHclrM0CuI58YrKPqworCmK6CYfzSc8UmXxXUe5dzND/DS9XgqDttQic2/OqTSAK63ynnrNqzr3D56VpDBeDeQjk3mc/0zmuFAPEXoAQoQKfD6HEuajvWJebQ6QIPgA TshqsnPlktbpftr4lsuB1tHS/W8D7SYVFMC/Kxy9QuYWs0cmRTtzfWEKIRHeDElOTQCX5JB5PgzVhhi5kYTi488Ba8j4zvNUw55hEoMxONYO7eMjJosmNjULsT492LGw3EfAgmgx9h3yFLQRZgfylg0h4PfLlcPOAdsnVX9/yLElD xu7Atwc4S7pBWTHvwue7PpRvWpTeqkU5sqiX4KcV5x8rk mBtxm48a8fsmp GNf 4IjwXu9cQaU9WLipiEnkqFsYo7/aAsmmKWBETyQg9BFXYK 165vrzSX8WTsv6ZZDnVjcE1n4Ov8Jl2cnAigoQbB0ROPpIRzZ3zH2diUv1vzlSuh9gbEJf3uQRKlYRVUbpboC0RbQ/7jgznfJAWyLykyDQ0EB8fVEOtbP1l4JEz39QwAU18ph3btnWWuKEV4 ghYvNG4m1DYntSF57s2ajRS6rPtR oYvGjrJL9zbHBhKHlfkIPC0TKotOCi96mqpikbBEfIZSomHxYgDwYCSvt60zaDIjlBxZ1UBdK JL0554Wia9W3Wg91bmYS9Q4SXMT8r4xGYB7OutEV24n7p088rVm/w2SZSiqlLqai539k6WGkzEQf19ytPtIE81a N z7aijTjy 7FCuVPF90svI5/NoGpSINqv84HUcMU71BvXUIT53Ea6CCpiWvvOPpo/XZar44emlIG0UgeB kfP6C6sis=

密码:

zRf7WZ3nM7ON{U0E6J5S}KpVm@k2ReDyq#1lG9go

CF代码:

<cfset str = "2M2WWOD4wANsGwWTmPqQIQYdz9WPwgiR0ntJHGaxm23b5a1sWUtCBcUQUMMtc9hvcYavJ6yqPgETOGZgDOdd9qjDwIb2aV9DLZT1iIcB3zNN5Ddhxd9iiui6TAlJxU/O">
<cfset tobas = tobase64(str)>
<cfset getFirst32bytes = Left(tobas,32)>
<cfset tobas2 = RemoveChars(tobas,1,32)>
<cfdump var="#tobas2#">
<cfset key = "zRf7WZ3nM7ON{U0E6J5S}KpVm@k2ReDyq##1lG9go">
<cfset x = hmac("#tobas2#","#key#","HMACSHA256")>
<cfset y = hmac("#getFirst32bytes#","#key#","HMACSHA256")>
<cfset decalgo = Left(x,16)>
<cfset decremainingData = RemoveChars(x,1,16)>
<cfset getDec = Decrypt(decalgo,"#key#","AES")>
<cfdump var="#x#"><br>
<cfdump var="#y#"><br>
<cfdump var="#decalgo#">
<cfdump var="#decremainingData#">
<cfdump var="#getDec#">

这是他们网站上的 java 示例:

private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding";
private static final String HASH_ALGORITHM = "SHA-256";
private static final String HMAC_ALGORITHM = "HmacSHA256";
private static final int IV_LENGTH = 16, HMAC_LENGTH = 32;
private static final Charset utf8 = Charset.forName("UTF-8");
private static final Provider bcProvider;
static {
   bcProvider = new BouncyCastleProvider();
   if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
    Security.addProvider(bcProvider);
   }
}

private static byte[] decrypt(byte[] key, byte[] data) throws GeneralSecurityException {
   byte[] decodedData = Base64.decode(data);
   if (decodedData == null || decodedData.length <= IV_LENGTH) {
    throw new RuntimeException("Bad input data.");
   }
   byte[] hmac = new byte[HMAC_LENGTH];
   System.arraycopy(decodedData, 0, hmac, 0, HMAC_LENGTH);
   if (!Arrays.equals(hmac,
     hmac(key, decodedData, HMAC_LENGTH, decodedData.length– HMAC_LENGTH))) {
    throw new RuntimeException("HMAC validation failed.");
   }
   byte[] iv = new byte[IV_LENGTH];
   System.arraycopy(decodedData, HMAC_LENGTH, iv, 0, IV_LENGTH);
   Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM, bcProvider);
   cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(hash(key), "AES"),
    new IvParameterSpec(iv));
   return cipher.doFinal(decodedData, HMAC_LENGTH + IV_LENGTH,
    decodedData.length– HMAC_LENGTH– IV_LENGTH);
}

private static byte[] hash(byte[] key) throws NoSuchAlgorithmException {
   MessageDigest md = MessageDigest.getInstance(HASH_ALGORITHM);
   md.update(key);
   return md.digest();
}

private static byte[] hmac(byte[] key, byte[] data, int offset, int length)
throws GeneralSecurityException {
   Mac mac = Mac.getInstance(HMAC_ALGORITHM, bcProvider);
   mac.init(new SecretKeySpec(key, HMAC_ALGORITHM));
   mac.update(data, offset, length);
   return mac.doFinal();
}

最佳答案

了解示例代码的重要一点是它指的是字节。您的 CF 代码正在使用字符。这似乎是一个微不足道的区别,但它们是完全不同的东西,会产生非常非常不同的结果。为了成功解密,您需要使用给定字符串的字节(或二进制)而不是字符。

虽然可以使用核心 CF 函数操作二进制数组,例如 arraySlice() ,语法有时会变得有点笨重。原因是二进制数组是与标准 CF 数组不同类型的对象,即 byte[] 与 java.util.List .因此,根据使用的功能,您可能需要 javacast将变量强制转换为预期类型。考虑到这一点..

第一部分-解密encKey
  1. Base64-decode the encKey.
  2. Remove the first 32 bytes of the decoded value. This is the HMAC (Hash Message Authentication Code). Calculate a SHA-256 HMAC of the rest of the decoded data using your API Shared Secret and compare it to the HMAC from the first 32 bytes.

首先使用binaryDecode将base64字符串转换成二进制.然后从返回的数组中提取适当数量的字节。这是预期的 HMAC 值:

hmacSize = 32;
binaryToDecrypt  = binaryDecode(encryptedKey, "base64");
expectedHMAC = binaryEncode( javacast("byte[]", arraySlice(binaryToDecrypt, 1, hmacSize))
                          , "hex" );

接下来,提取所有剩余字节,并使用它们计算实际的 HMAC。根据预期值验证它。如果两者不匹配,则说明出了问题。

remainData = arraySlice(binaryToDecrypt, hmacSize + 1);
actualHMAC = hmac( javacast("byte[]", remainData ), sharedSecret, "HMACSHA256");

if (compare(actualHMAC, expectedHMAC) != 0) {
    throw("ERROR: Invalid HMAC ["& actualHMAC &"]. Expected ["& expectedHMAC &"]");
}
  1. The next 16 bytes should be removed and used as the IV (Initialization Vector) for the decryption algorithm.

其余字节包含一个 IV,后跟加密值。在解密后者之前,您需要将两者提取和分离:

ivSize = 16;
ivValue = javacast("byte[]", arraySlice(remainData, 1, ivSize));
encryptedValue = javacast("byte[]", arraySlice(remainData, ivSize + 1));
  1. Decrypt the remaining data using AES-256-CBC, the IV from step 3, and the SHA-256 hash of your API Shared Secret.

解密之前的最后一步是通过散列共享 secret 生成解密 key 。不幸的是,CF的hash()函数总是返回一个十六进制字符串。所以必须转换成base64格式才能兼容解密功能。​​

keyHex = hash(sharedSecret, "SHA-256", "utf-8");
keyBase64 = binaryEncode(binaryDecode(keyHex, "hex"), "base64");

最后用这三个值全部解密。返回的二进制文件将包含第二部分中使用的加密 key 。

decryptedKeyBinary = decryptBinary( encryptedValue
                           , keyBase64
                           , "AES/CBC/PKCS5Padding"
                           , ivValue);

第二部分 - 解密 encPaymentData

使用与第一部分完全相同的过程,只是交换变量:

  • 使用encPaymentData代替encryptedKey
  • 使用 decryptedKeyBinary 而不是 sharedSecret

最终的解密结果将是二进制的。使用 charsetEncode将其转换回人类可读的字符串:

result = charsetEncode(decryptedResult, "utf-8");

注意:您发布的示例值似乎已损坏,因为它们甚至不适用于 java 示例。当使用有效值(键、数据等)时,上述步骤会产生正确的结果。

关于java - 使用 Visa Checkout 解密支付数据,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42010774/

相关文章:

java 。带参数的 Singleton 和 getInstance

java - Firestore 生成的 key 与集合中的自定义 key ?

ios - 安全地存储应用内购买

arrays - 如何使用 Coldfusion 读取/循环 URL 中的数组

java - 使用 ICUTokenizer JAVA 标记泰语句子

php - 如何使用 Laravel Eloquent ORM 搜索加密值?

javascript - 使用Javascript库SJCL加密,使用PHP解密

cfscript 标记输出中的 xml 注释

security - 修复 ColdFusion 中的 Unicode 转换问题/漏洞

java - 不区分大小写的字符串 split() 方法