为 ruby 获取此 "RSA/ECB/OAEPWithSHA-256AndMGF1Padding" 组合

Get this "RSA/ECB/OAEPWithSHA-256AndMGF1Padding" combination for ruby

我有这个java加解密的代码,我要change/convert到Ruby的代码。我在 OpenSSL gem 中查找,但找不到 ruby 可用的“RSA/ECB/OAEPWithSHA-256AndMGF1Padding”组合。我该如何实施?

public class EncryptDecryptService {
    
    public String encryptRequestObject(RequestObject requestObject) throws UnsupportedEncodingException, FileNotFoundException, CertificateException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException {
        PublicKey publicKey = getPublicKey(requestObject.getKeyFilename());
        byte[] message = requestObject.getString().getBytes("UTF-8");
        byte[] secret = encrypt(publicKey, message);
        return Base64.encodeBase64String(secret);
    }
    
    public String decryptRequestObject(RequestObject requestObject) throws UnrecoverableKeyException, KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, InvalidKeyException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException {
        PrivateKey privateKey = getPrivateKey(requestObject.getKeyFilename(), requestObject.getKeyPassword());
        byte[] cipherText = Base64.decodeBase64(requestObject.getString());
        byte[] decrypted = decrypt(privateKey, cipherText);
        return new String(decrypted, "UTF-8");
    }
    
    private PublicKey getPublicKey(String filename) throws FileNotFoundException, CertificateException {
        FileInputStream fin = new FileInputStream(filename);
        CertificateFactory factory = CertificateFactory.getInstance("X.509");
        X509Certificate certificate = (X509Certificate) factory.generateCertificate(fin);
        PublicKey publicKey = certificate.getPublicKey();
        return publicKey;
    }
    
    private PrivateKey getPrivateKey(String filename, String password) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, UnrecoverableKeyException {
        FileInputStream fin = new FileInputStream(filename);
        KeyStore ks = KeyStore.getInstance("pkcs12");
        ks.load(fin, password.toCharArray());
        String str = ks.aliases().nextElement();
        PrivateKey privateKey = (PrivateKey) ks.getKey(str, password.toCharArray());
        return privateKey;
    }
    
    private byte[] encrypt(PublicKey key, byte[] plainText) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
        Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
        cipher.init(Cipher.ENCRYPT_MODE, key);
        return cipher.doFinal(plainText);
    }
    
    private byte[] decrypt(PrivateKey key, byte[] cipherText) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
        Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
        cipher.init(Cipher.DECRYPT_MODE, key);
        return cipher.doFinal(cipherText);
    }
}

OAEP 使用多个参数,包括两个摘要,一个用于 OAEP(即用于散列 OAEP 标签),一个用于掩码生成函数 (MGF1),参见 RFC8017, sec. 7.1

标识符 RSA/ECB/OAEPWithSHA-256AndMGF1Padding 不明确且取决于提供商。例如,SunJCE 提供程序使用 SHA-256 作为 OAEP 摘要,使用 SHA-1 作为 MGF1 摘要,BouncyCastle 提供程序使用 SHA-256 作为两种摘要。

下面是用Java码加密,用Ruby码解密的例子(反方向模拟)


在 Java 方面,SunJCE 提供者使用 WLOG 并确定所涉及的摘要:

String pubKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNvs/qUMjkfq2E9o0qn03+KJE7" + 
                "ASczEbn6q+kkthNBdmTsskikWsykpDPnLWhAVkmjz4alQyqw+mHYP9xhx8qUC4A3" + 
                "tXY0ObxANUUKhUvR7zNj4vk4t8F2nP3erWvaG8J+sN3Ubr40ZYIYLS6UHYRFrqRD" + 
                "CDhUtyjwERlz8KhLyQIDAQAB";

KeyFactory keyFactory = KeyFactory.getInstance("RSA");
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(pubKey));
RSAPublicKey publicKey = (RSAPublicKey) keyFactory.generatePublic(keySpec);

byte[] plaintext = "The quick brown fox jumps over the lazy dog".getBytes(StandardCharsets.UTF_8);
byte[] ciphertext = encrypt(publicKey, plaintext);

System.out.println("Ciphertext: " + Base64.getEncoder().encodeToString(ciphertext));

private static byte[] encrypt(PublicKey key, byte[] plainText) throws Exception {
    Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding"); 
    cipher.init(Cipher.ENCRYPT_MODE, key);
    OAEPParameterSpec oaepParameterSpec = cipher.getParameters().getParameterSpec(OAEPParameterSpec.class);
    MGF1ParameterSpec mgf1ParameterSpec = (MGF1ParameterSpec)oaepParameterSpec.getMGFParameters();
    System.out.println("Provider  : " + cipher.getProvider().getName());
    System.out.println("OAEP-Hash : " + oaepParameterSpec.getDigestAlgorithm());
    System.out.println("MGF1-Hash : " + mgf1ParameterSpec.getDigestAlgorithm());
    return cipher.doFinal(plainText);
}

对应于发布的 encrypt() 方法(附加输出除外)。该代码产生(例如)以下输出:

Provider  : SunJCE
OAEP-Hash : SHA-256
MGF1-Hash : SHA-1
Ciphertext: WlozD9ojNRQafip41dpuuhBMe7ruH2FBWnMhbAaSuAtPDpHOUyKaAm6mO15BbvL3eTXyqfEQx29dYPJEbUr5T/WXs846PQN6g7Yv25EXGVbPCzc4aIbms76C1jP92wXNEGWMnu624Fq5W9MVXX75mfaY0Fjvrh5k/TFuO4AIxMk=

为了完整性,应该提到的是,也可以通过以下方式明确指定参数:

Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPPadding"); 
OAEPParameterSpec oaepParameterSpec = new OAEPParameterSpec("SHA-256", "MGF1", new MGF1ParameterSpec("SHA-1"), PSource.PSpecified.DEFAULT);
cipher.init(Cipher.ENCRYPT_MODE, key, oaepParameterSpec);

由于上述矛盾,此明确规范是更稳健的替代方案。


确定摘要后(因为提供者已知或明确具有上述输出)可以进行 Ruby 实施。

Ruby 的可能 OAEP 实施是 openssl-oaep

这样,解密的Ruby代码可以实现如下:

require 'openssl'
require 'openssl/oaep'
require 'base64'

private_key = 
'-----BEGIN PRIVATE KEY-----
MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAM2+z+pQyOR+rYT2
jSqfTf4okTsBJzMRufqr6SS2E0F2ZOyySKRazKSkM+ctaEBWSaPPhqVDKrD6Ydg/
3GHHypQLgDe1djQ5vEA1RQqFS9HvM2Pi+Ti3wXac/d6ta9obwn6w3dRuvjRlghgt
LpQdhEWupEMIOFS3KPARGXPwqEvJAgMBAAECgYADxGqqL7B9/pPOy3TqQuB6tuNx
4SOGm9x76onqUisoF7LhYqJR4Be/LAKHSR2PkATpKvOcMw6lDvCbtQ+j+rSK2PkN
4iDi1RYqbLUbZBS8vhrgU0CPlmgSSp1NBsqMK9265CaJox3frxmBK1yuf22RboIK
pqOzcluuA4aqLegmwQJBAP0+gM/tePzx+53DrxpYQvlfi9UJo7KeqIFL8TjMziKt
EaRGeOZ6UX/r6CQHojYKnNti7pjAwonsdwCTcv1yy7sCQQDP+/ww49VFHErON/MO
w5iYCsrM5Lx+Yc2JAjetCDpkMrRT92cgQ0nxR5+jNeh+gE2AmB9iKlNxsHJoRaPQ
lBRLAkEAl9hiZEp/wStXM8GhvKovfldMAPFGtlNrthtTCDvFXgVoDpgy5f9x3sIU
74WkPcMfSmyHpA/wlcKzmCTRTicHAQJBALUjq7MQ2tAEIgqUo/W52I6i55mnpZsU
pyOqcL8cqW5W0sNGd+SbdizTym8lJkX2jIlw8/RVFLOxjxLNhCzGqx0CQQDeUMnw
7KGP3F7BnbsXCp64YDdihzSO5X/Mfwxw6+S/pyKZ0/X4uwt24kZuoDnFzGWJYlea
sDQC6enIru+ne5es
-----END PRIVATE KEY-----'

key = OpenSSL::PKey::RSA.new(private_key) 
label = ''
md_oaep = OpenSSL::Digest::SHA256
md_mgf1 = OpenSSL::Digest::SHA1
cipher_text_B64 = 'WlozD9ojNRQafip41dpuuhBMe7ruH2FBWnMhbAaSuAtPDpHOUyKaAm6mO15BbvL3eTXyqfEQx29dYPJEbUr5T/WXs846PQN6g7Yv25EXGVbPCzc4aIbms76C1jP92wXNEGWMnu624Fq5W9MVXX75mfaY0Fjvrh5k/TFuO4AIxMk='
cipher_text = Base64.decode64(cipher_text_B64)
plain_text = key.private_decrypt_oaep(cipher_text, label, md_oaep, md_mgf1)
print(plain_text) # The quick brown fox jumps over the lazy dog

以原始明文作为输出