我正在开发一个通过 TPV Redsys API 进行付款的 iOS 应用程序。我正在关注文档,但它不起作用(服务器由于签名不正确而返回错误),我猜这是因为 3DES 加密。
我正在使用文档中的测试数据,因此输出应该与文档中的相同。
这是我的代码:
- (void) payViaTPVWithAmount:(NSString *)amount andOrderId:(NSString *)orderId
{
// I don't use my amount and orderId but those provided by the documentation to test
// We need to obtain 3 fields: DS_SIGNATURE_VERSION, DS_MERCHANTPARAMETERS and DS_SIGNATURE.
// 1) Obtaining DS_SIGNATURE_VERSION
NSString *dsSignatureVersion = @"HMAC_SHA256_V1";
// >>>> This step is easy and of course is correct <<<<
// 2) Obtaining DS_MERCHANTPARAMETERS
// 2.1) Build params JSON -using test data-
NSString *params = [NSString stringWithFormat: @"{\"DS_MERCHANT_AMOUNT\":\"%@\",\"DS_MERCHANT_ORDER\":\"%@\",\"DS_MERCHANT_MERCHANTCODE\":\"%@\",\"DS_MERCHANT_CURRENCY\":\"978\",\"DS_MERCHANT_TRANSACTIONTYPE\":\"0\",\"DS_MERCHANT_TERMINAL\":\"%@\",\"DS_MERCHANT_MERCHANTURL\":\"%@\",\"DS_MERCHANT_URLOK\":\"%@\",\"DS_MERCHANT_URLKO\":\"%@\"}",
@"145",
@"1446117555",
@"327234688",
@"1",
@"http:\\/\\/www.bancsabadell.com\\/urlNotificacion.php",
@"http:\\/\\/www.bancsabadell.com\\/urlOK.php",
@"http:\\/\\/www.bancsabadell.com\\/urlKO.php"];
//2.2) convert JSON params to base64
NSData *paramsData = [params dataUsingEncoding:NSUTF8StringEncoding];
NSString *paramsBase64 = [paramsData base64EncodedStringWithOptions:0];
NSLog(@"apiMacSHA256\n%@", params);
NSLog(@"apiMacSHA256Base64\n%@", paramsBase64);
// >>>>> This output is identical to that included in the documents so this step 2 is ok. <<<<<
// 3) Obtaining DS_SIGNATURE
// 3.1) start with secret key -the document gives this for test purposes-
NSString *merchantKey = @"sq7HjrUOBfKmC576ILgskD5srU870gJ7"; //32 bytes
// 3.2) convert it to base64
NSData *merchantKeyData = [merchantKey dataUsingEncoding:NSUTF8StringEncoding];
NSString *merchantKeyBase64 = [merchantKeyData base64EncodedStringWithOptions:0];
// 3.3) the documentation doesn't say to convert this key to Hex but I have the Android code (which is working) and it has this step.
// Anyway I've tested with both options, with this step and without this step.
NSData *merchantKeyBase64Data = [merchantKeyBase64 dataUsingEncoding:NSUTF8StringEncoding];
NSString *merchantHex = [merchantKeyBase64Data hexadecimalString];
// 3.4) get 3DES from orderId and base 64 (or Hex depending on if step 3.3 is done) key just obtained
NSData *operationKey = [self encrypt_3DSWithKey:merchantHex andOrderId:@"1446117555"];
// 3.5) get HMAC SHA256 from operationkey and ds_merchantparameters
NSData *signatureHmac256 = [self mac256WithKey:operationKey andString:paramsBase64];
// 3.6) convert HMAC SHA256 to base64
NSString *signatureHmac256Base64 = [signatureHmac256 base64EncodedStringWithOptions:0];
NSLog(@"signatureHmac256Base64\n%@", signatureHmac256Base64);
// This step is not working, the signature is not the correct one and the connection to the TPV Redsys system fails.
// 4) Build the request
// Set URL - using URL for development purposes-
NSURL *url = [NSURL URLWithString:@"https://sis-t.redsys.es:25443/sis/realizarPago"];
NSString *dsSignatureVersionEncoded = [dsSignatureVersion stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLUserAllowedCharacterSet]];
NSString *paramsHTTPEncoded = [paramsBase64 stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLUserAllowedCharacterSet]];
NSString *signatureHmac256HTTPEncoded = [signatureHmac256Base64 stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLUserAllowedCharacterSet]];
NSString *body = [NSString stringWithFormat:@"DS_SIGNATURE=%@&DS_MERCHANTPARAMETERS=%@&DS_SIGNATUREVERSION=%@", signatureHmac256HTTPEncoded, paramsHTTPEncoded, dsSignatureVersionEncoded];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc]initWithURL: url];
[request setHTTPMethod: @"POST"];
[request setHTTPBody: [body dataUsingEncoding: NSUTF8StringEncoding allowLossyConversion:false]];
[_webViewer loadRequest:request];
}
- (NSData *)encrypt_3DSWithKey:(NSString *)key andOrderId:(NSString *)str
{
NSData *plainData = [str dataUsingEncoding:NSUTF8StringEncoding];
NSData *keyData = [key dataUsingEncoding:NSUTF8StringEncoding];
size_t bufferSize = plainData.length + kCCKeySize3DES;
NSMutableData *cypherData = [NSMutableData dataWithLength:bufferSize];
size_t movedBytes = 0;
CCCryptorStatus ccStatus;
ccStatus = CCCrypt(kCCDecrypt,
kCCAlgorithm3DES,
kCCOptionECBMode,
keyData.bytes,
kCCKeySize3DES,
NULL,
plainData.bytes,
plainData.length,
cypherData.mutableBytes,
cypherData.length,
&movedBytes);
cypherData.length = movedBytes;
if(ccStatus == kCCSuccess)
{
NSLog(@"Data: %@",cypherData);
}
else
{
NSLog(@"Failed DES decrypt, status: %d", ccStatus);
}
return cypherData;
}
- (NSData *)mac256WithKey:(NSData *)key andString:(NSString *)str
{
NSData *strData = [str dataUsingEncoding:NSUTF8StringEncoding];
NSMutableData* hash = [NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH ];
CCHmac(kCCHmacAlgSHA256, key.bytes, key.length, strData.bytes, strData.length, hash.mutableBytes);
return hash;
}
这个版本失败是因为我由于对齐而出现加密错误,所以我将加密修改为:
NSString *initVec = @"\0\0\0\0\0\0\0\0";
const void *vinitVec = (const void *) [initVec UTF8String];
CCCryptorStatus ccStatus;
ccStatus = CCCrypt(kCCDecrypt,
kCCAlgorithm3DES,
kCCOptionPKCS7Padding | kCCOptionECBMode,
keyData.bytes,
kCCKeySize3DES,
vinitVec,
plainData.bytes,
plainData.length,
cypherData.mutableBytes,
cypherData.length,
&movedBytes);
这次加密似乎成功了,但输出不是预期的。
我尝试过使用此代码那么问题 https://stackoverflow.com/questions/33799494/3des-encrypt-result-in-php-java-and-net-produces-different-result-from-3des-io但它也失败了(签名不正确并且连接失败)。
这段代码有错误吗?
我知道这很困难,但是使用 TPV Redsys API 成功付款的人可以看到问题出在哪里?
EDIT 1:
这是文档中关于如何获取 DS_SIGNATURE 的内容:
对请求数据进行签名
获得要签名的数据字符串和终端特定密钥后,必须按照以下步骤计算签名:
每次操作都会生成一个特定的密钥。为了获得
操作中要使用的特定键,必须执行
商户密钥之间的3DES加密,必须是
先前以 BASE 64 解码,以及订单 Id 的值 (Ds_Merchant_Order)。
HMAC SHA256 必须根据参数值计算
Ds_MerchantParameters和上一步获取的key。
获得的结果必须以 BASE 64 进行编码,并且该结果
编码将是 Ds_Signature 参数的值。
由于我已经获得了适用于 Java 的 Redsys API SDK(没有适用于 iOS 的 SKD),这就是它生成 Ds_Signature 参数的作用:
public String createMerchantSignature(final String merchantKey) throws Exception {
String merchantParams = createMerchantParameters();
byte [] key = decodeB64(merchantKey.getBytes("UTF-8"));
String secretKc = toHexadecimal(key, key.length);
byte [] secretKo = encrypt_3DES(secretKc, getOrder());
// MAC with the operation key "Ko" and then coded in BASE64
byte [] hash = mac256(merchantParams, secretKo);
String res = encodeB64String(hash);
return res;
}
服务器期望的是这样的:
预期 Ds_SignatureVersion
HMAC SHA256 _V1
预期 Ds_MerchantParameters
eyJEU19NRVJDSEFOVF9BTU9VTlQiOiIxNDUiLCJEU19NRVJDSEFOVF9PUkRFUiI6IjE0NDYwNjg1ODEiLCJEU19NRVJDSEFOVF9NRVJDSEFOVENPREUiOizMjcyMzQ2ODgiLCJEU19NRVJDSEFOVF9DVVJSRU5DWSI6Ij k3OCISIkRTX01FUkNIQU5UX1RSQU5TQUNUSU9OVFlQRSI6IjAiLCJEU19NRVJDSEFOVF9URVJNSU5BTCI6IjEiLCJEU19NRVJDSEFOVF9NRVJDSEFOVFVSTCI6Imh0dHA6XC9cL3d3dy5iYW5jc2FiYWRlbGwuY29tXC9 1cmxOb3RpZmljYWNpb24ucGhwIiwiRFNfTUVSQ0hBTlRfVVJMT0siOiJodHRwOlwvXC93d3cuYmFuY3NhYmFkZWxsLmNvbVwvdXJsT0sucGhwIiwiRFNfTUVSQ0hBTlRfVVJMS08iOiJodHRwO lwvXC93d3cuYmFuY3NhYmFkZWxsLmNvbVwvdXJSS08ucGhwIiwiRFNfTUVSQ0hBTlRfUEFOIjoiNDU0ODgxMjA0OTQwMDAwNCISIkRTX01FUkNIQU5UX0VYUELSWURBVEUiOiIxNTEyIiwiRFNfTUVSQ0h BTLRfQ1ZWMiI6IjEyMyJ9
通过我的代码,我得到了这个精确的 base64 字符串,所以代码没问题。
预期 Ds_Signature
QfLVUv4nF2Nw7jBAkw0w8H0eRlwh2E1w/ZlKHdA2Sq0=
使用我的代码,我的 Ds_Signature 比预期的要长:
ly2hYyjVlXQF%2FvgdEXBOj0obUdC7r5IERdEpLPSPksA=
EDIT 2:
我根据 zaph 的评论做了一些更改。我删除了 URL 的编码,并重新计算了步骤 3.2 和 3.3。这是我现在的代码:
- (void) payViaTPVWithAmount:(NSString *)amount andOrderId:(NSString *)orderId
{
// I don't use my amount and orderId but those provided by the documentation to test
// We need to obtain 3 fields: DS_SIGNATURE_VERSION, DS_MERCHANTPARAMETERS and DS_SIGNATURE.
// 1) Obtaining DS_SIGNATURE_VERSION
NSString *dsSignatureVersion = @"HMAC_SHA256_V1";
// >>>> This step is easy and of course is correct <<<<
// 2) Obtaining DS_MERCHANTPARAMETERS
// 2.1) Build params JSON -using test data-
NSString *params = [NSString stringWithFormat: @"{\"DS_MERCHANT_AMOUNT\":\"%@\",\"DS_MERCHANT_ORDER\":\"%@\",\"DS_MERCHANT_MERCHANTCODE\":\"%@\",\"DS_MERCHANT_CURRENCY\":\"978\",\"DS_MERCHANT_TRANSACTIONTYPE\":\"0\",\"DS_MERCHANT_TERMINAL\":\"%@\",\"DS_MERCHANT_MERCHANTURL\":\"%@\",\"DS_MERCHANT_URLOK\":\"%@\",\"DS_MERCHANT_URLKO\":\"%@\"}",
@"145",
@"1446117555",
@"327234688",
@"1",
@"http:\\/\\/www.bancsabadell.com\\/urlNotificacion.php",
@"http:\\/\\/www.bancsabadell.com\\/urlOK.php",
@"http:\\/\\/www.bancsabadell.com\\/urlKO.php"];
//2.2) convert JSON params to base64
NSData *paramsData = [params dataUsingEncoding:NSUTF8StringEncoding];
NSString *paramsBase64 = [paramsData base64EncodedStringWithOptions:0];
NSLog(@"apiMacSHA256\n%@", params);
NSLog(@"apiMacSHA256Base64\n%@", paramsBase64);
// >>>>> This output is identical to that included in the documents so this step 2 is ok. <<<<<
// 3) Obtaining DS_SIGNATURE
// 3.1) start with secret key -the document gives this for test purposes-
NSString *merchantKey = @"sq7HjrUOBfKmC576ILgskD5srU870gJ7"; //32 bytes
// 3.2) convert it to base64
NSData *merchantKeyData = [merchantKey dataUsingEncoding:NSUTF8StringEncoding];
NSData *merchantKeyBase64Data = [merchantKeyData base64EncodedDataWithOptions:0];
// 3.3) the documentation doesn't say to convert this key to Hex but I have the Android code (which is working) and it has this step.
// Anyway I've tested with both options, with this step and without this step.
NSString *merchantHex = [merchantKeyBase64Data hexadecimalString];
// 3.4) get 3DES from orderId and base 64 (or Hex depending on if step 3.3 is done) key just obtained
NSData *operationKey = [self encrypt_3DSWithKey:merchantHex andOrderId:@"1446117555"];
// 3.5) get HMAC SHA256 from operationkey and ds_merchantparameters
NSData *signatureHmac256 = [self mac256WithKey:operationKey andString:paramsBase64];
// 3.6) convert HMAC SHA256 to base64
NSString *signatureHmac256Base64 = [signatureHmac256 base64EncodedStringWithOptions:0];
NSLog(@"signatureHmac256Base64\n%@", signatureHmac256Base64);
// This step is not working, the signature is not the correct one and the connection to the TPV Redsys system fails.
// 4) Build the request
// Set URL - using URL for development purposes-
NSURL *url = [NSURL URLWithString:@"https://sis-t.redsys.es:25443/sis/realizarPago"];
NSString *body = [NSString stringWithFormat:@"DS_SIGNATURE=%@&DS_MERCHANTPARAMETERS=%@&DS_SIGNATUREVERSION=%@", signatureHmac256HTTPEncoded, paramsHTTPEncoded, dsSignatureVersionEncoded];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc]initWithURL: url];
[request setHTTPMethod: @"POST"];
[request setHTTPBody: [body dataUsingEncoding: NSUTF8StringEncoding allowLossyConversion:false]];
[_webViewer loadRequest:request];
}
- (NSData *)encrypt_3DSWithKey:(NSString *)key andOrderId:(NSString *)str
{
NSData *plainData = [str dataUsingEncoding:NSUTF8StringEncoding];
NSData *keyData = [key dataUsingEncoding:NSUTF8StringEncoding];
size_t bufferSize = plainData.length + kCCKeySize3DES;
NSMutableData *cypherData = [NSMutableData dataWithLength:bufferSize];
size_t movedBytes = 0;
NSString *initVec = @"\0\0\0\0\0\0\0\0";
const void *vinitVec = (const void *) [initVec UTF8String];
CCCryptorStatus ccStatus;
ccStatus = CCCrypt(kCCDecrypt,
kCCAlgorithm3DES,
kCCOptionPKCS7Padding | kCCOptionECBMode,
keyData.bytes,
kCCKeySize3DES,
vinitVec,
plainData.bytes,
plainData.length,
cypherData.mutableBytes,
cypherData.length,
&movedBytes);
cypherData.length = movedBytes;
if(ccStatus == kCCSuccess)
{
NSLog(@"Data: %@",cypherData);
}
else
{
NSLog(@"Failed DES decrypt, status: %d", ccStatus);
}
return cypherData;
}
- (NSData *)mac256WithKey:(NSData *)key andString:(NSString *)str
{
NSData *strData = [str dataUsingEncoding:NSUTF8StringEncoding];
NSMutableData* hash = [NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH ];
CCHmac(kCCHmacAlgSHA256, key.bytes, key.length, strData.bytes, strData.length, hash.mutableBytes);
return hash;
}
我的 Ds_Signature 现在是 R8+4R6diEbm3nJR6KmonYDy53Zi4CZpuxdoMZtucGX4= (与预期不同),连接仍然失败。