C#与Ruby间AES和SHA512加解密对接银行异常排查
AES-256-CBC加解密对接银行异常排查:开头乱码与500错误
问题概述
基于参考C#代码实现Ruby版本的AES-256-CBC与SHA512加解密逻辑,自测加解密功能正常,但对接银行时出现两个异常:
- 向银行发送加密结果时返回500错误
- 解密银行提供的示例数据时,结果开头出现特殊乱码,无法得到正确JSON格式内容
实现代码
require 'openssl' require 'base64' class SecurityManager PASSWORD = 'example'.freeze CERT_PATH = Rails.root.join('config', 'documents', 'EXAMPLE_EMPRESA.pfx').to_s.freeze def self.encrypt(plain_text) return nil if plain_text.nil? bytes_to_be_encrypted = plain_text.encode('UTF-8') aux = get_thumbprint_from_certificate key = aux + PASSWORD password_bytes = key.encode('UTF-8') password_bytes = OpenSSL::Digest.new('SHA512').digest(password_bytes) bytes_encrypted = encrypt_internal(bytes_to_be_encrypted, password_bytes) Base64.strict_encode64(bytes_encrypted) end def self.decrypt(encrypted_text) return nil if encrypted_text.nil? bytes_to_be_decrypted = Base64.strict_decode64(encrypted_text) thumbprint = get_thumbprint_from_certificate key = thumbprint + PASSWORD password_bytes = key.encode('UTF-8') password_bytes = OpenSSL::Digest.new('SHA512').digest(password_bytes) bytes_decrypted = decrypt_internal(bytes_to_be_decrypted, password_bytes) bytes_decrypted.force_encoding('UTF-8') end def self.encrypt_internal(bytes_to_be_encrypted, password_bytes) salt_bytes = [1, 2, 3, 4, 5, 6, 7, 8].pack('C*') cipher = OpenSSL::Cipher.new('AES-256-CBC') cipher.encrypt key = OpenSSL::PKCS5.pbkdf2_hmac(password_bytes, salt_bytes, 1000, cipher.key_len, 'sha1') cipher.key = key[0, 32] cipher.iv = key[0, 16] cipher.update(bytes_to_be_encrypted) + cipher.final end def self.decrypt_internal(bytes_to_be_decrypted, password_bytes) salt_bytes = [1, 2, 3, 4, 5, 6, 7, 8].pack('C*') cipher = OpenSSL::Cipher.new('AES-256-CBC') cipher.decrypt key = OpenSSL::PKCS5.pbkdf2_hmac(password_bytes, salt_bytes, 1000, cipher.key_len, 'sha1') cipher.key = key[0, 32] cipher.iv = key[0, 16] cipher.update(bytes_to_be_decrypted) + cipher.final end def self.thumbprint_from_certificate pfx_data = File.read(CERT_PATH) pfx = OpenSSL::PKCS12.new(pfx_data, PASSWORD) cert = pfx.certificate OpenSSL::Digest::SHA1.new(cert.to_der).hexdigest end end
测试结果
加密示例输出
MRehs7Hr5+hN5ug8fhpsIrmlXgqChPAs82lYUvIw1gVhSM56MjlWSk5JNP3UvqKsJw1Grb6C2Q5pKqvyKPiq2W8cr0ZAQBS2aqP0RSD/D4m2ID4oxZix4b5ZljPd2899
解密银行示例的异常结果
"\xBF\xF2\xF1\xC9}\xBC\xBF\x91U\xF9nn\f\xFA\xF6\xA8essage":"Los datos personales del usuario son incorrectas.","body":null}"
预期解密结果
"{\"message\":\"Los datos personales del usuario son incorrectas.\",\"body\":null}"
另一银行示例解密异常结果
"\xBFݒ\x9A\u0012\xF7\xBD\x89P\xF7om\b\xB8\xAD\x8Cd": 2295, "password": "example", "documentNumber": "XXXXXXX", "documentType": "Q", "documentExtension": "LP", "documentComplement": "", "amount": 0.3, "currency": "BOL", "fundSource": "Ventas de tarjetas de regalo", "fundDestination": "Comercios que realizaron las ventas", "sourceAccount": "2011040905323", "sourceCurrency": "BOL", "description": "Pagos por ventas efectuadas", "sendVouchers": "info@example.com", "cismartApprovers": [ { "idc": "04011574-Q-PO", "type": 1 } ], "spreadsheet": { "formProvidersPayments": [ { "paymentType": "PROV", "line": 1, "accountNumber": "2011040905323", "glossPayment": "Pago por ventas", "amount": 0.1, "documentType": "Q", "documentNumber": "0280000", "documentExtension": "", "firstDetail": "detalle 1", "secondDetail": "detalle 2", "mail": "" } ], "formAchPayments": [ { "paymentType": "ACH", "line": 1, "accountNumber": "5555555555", "titularName": "nombre de titular", "firstLastName": "", "secondLastName": "", "amount": 0.2, "branchOfficeId": 201, "firstDetail": "detalle", "mail": "", "bankId": "8888" } ], "formOddPayments": [ { "paymentType": "ODD", "line": 1, "accountNumber": "cuentaAdebitar", "titularName": "William", "firstLastName": "Prueba", "secondLastName": "Prueba", "description": "pruebaDescripcion", "glossPayment": "pruebaGlosa", "amount": 0.3, "documentType": "Q", "documentNumber": "7894561", "DocumentExtension": "LP", "branchOfficeId": 201, "firstDetail": "DET ODD", "SecondDetail": "SEC ODD", "mail": "example@examplecom", "bankId": "8888", "commission": 0.1, "CommissionCurrency": "BOL" } ] } }"
排查与修复方案
1. 核心问题:IV生成与密文格式不匹配
当前代码直接使用密钥的前16位作为IV,不符合CBC模式规范:
- CBC模式要求IV必须随机且与密钥独立,不能从密钥派生
- 银行的C#实现大概率是将随机生成的IV与密文拼接后再Base64编码,解密时需要先拆分出IV
修复加密逻辑:
def self.encrypt_internal(bytes_to_be_encrypted, password_bytes) salt_bytes = [1, 2, 3, 4, 5, 6, 7, 8].pack('C*') cipher = OpenSSL::Cipher.new('AES-256-CBC') cipher.encrypt # 生成随机IV cipher.random_iv iv = cipher.iv key = OpenSSL::PKCS5.pbkdf2_hmac(password_bytes, salt_bytes, 1000, cipher.key_len, 'sha1') cipher.key = key[0, 32] # 拼接IV和密文后返回 iv + cipher.update(bytes_to_be_encrypted) + cipher.final end
修复解密逻辑:
def self.decrypt_internal(bytes_to_be_decrypted, password_bytes) salt_bytes = [1, 2, 3, 4, 5, 6, 7, 8].pack('C*') cipher = OpenSSL::Cipher.new('AES-256-CBC') cipher.decrypt # 拆分IV(前16字节)和密文 iv = bytes_to_be_decrypted[0, 16] ciphertext = bytes_to_be_decrypted[16..-1] key = OpenSSL::PKCS5.pbkdf2_hmac(password_bytes, salt_bytes, 1000, cipher.key_len, 'sha1') cipher.key = key[0, 32] cipher.iv = iv cipher.update(ciphertext) + cipher.final end
2. 证书Thumbprint大小写不一致
C#中证书的SHA1 Thumbprint默认是大写十六进制字符串,而Ruby的hexdigest返回小写,会导致初始密钥字符串不一致,修正如下:
def self.thumbprint_from_certificate pfx_data = File.read(CERT_PATH) pfx = OpenSSL::PKCS12.new(pfx_data, PASSWORD) cert = pfx.certificate # 转成大写,匹配C#的Thumbprint格式 OpenSSL::Digest::SHA1.new(cert.to_der).hexdigest.upcase end
3. 方法名笔误修正
代码中encrypt和decrypt方法调用的是get_thumbprint_from_certificate,但实际定义的方法是thumbprint_from_certificate,需要统一:
# 将encrypt中的aux = get_thumbprint_from_certificate改为 aux = thumbprint_from_certificate # 将decrypt中的thumbprint = get_thumbprint_from_certificate改为 thumbprint = thumbprint_from_certificate
4. PBKDF2参数验证
确认参考C#代码的PBKDF2参数:
- 迭代次数:当前是1000,需与C#的
Rfc2898DeriveBytes迭代次数一致 - 哈希算法:当前用的是SHA1,若C#代码指定了SHA512,需改为
'sha512' - Salt值:当前是固定的
[1,2,3,4,5,6,7,8],需与C#代码的salt完全一致
验证建议
- 用修复后的代码加密一段测试文本,再用C#参考代码解密,确认结果一致
- 用银行提供的示例加密数据,用修复后的代码解密,确认无开头乱码且JSON格式正确
- 重新向银行发送加密结果,验证500错误是否解决
内容的提问来源于stack exchange,提问作者Carlos Rodriguez
相关产品推荐
相关产品推荐

