You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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完全一致

验证建议

  1. 用修复后的代码加密一段测试文本,再用C#参考代码解密,确认结果一致
  2. 用银行提供的示例加密数据,用修复后的代码解密,确认无开头乱码且JSON格式正确
  3. 重新向银行发送加密结果,验证500错误是否解决

内容的提问来源于stack exchange,提问作者Carlos Rodriguez

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 05:09:55