I have this java code for encryption and decryption, which I want to change/convert to Ruby code. I looked up in OpenSSL gem but dint find the "RSA/ECB/OAEPWithSHA-256AndMGF1Padding" combination available for ruby. How do I implement it?
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);
}
}
CodePudding user response:
OAEP uses several parameters, including two digests, one for OAEP (i.e. for hashing the OAEP label) and one for the mask generation function (MGF1), see RFC8017, sec. 7.1.
The identifier RSA/ECB/OAEPWithSHA-256AndMGF1Padding
is ambiguous and depends on the provider. For example the SunJCE provider uses SHA-256 as OAEP digest and SHA-1 as MGF1 digest, the BouncyCastle provider uses SHA-256 for both digests.
The following is an example of the encryption with the Java code and the decryption with the Ruby code (the opposite direction is analog).
On the Java side the SunJCE provider is used WLOG and the digests involved are determined:
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));
with
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);
}
which corresponds to the posted encrypt()
method (except for the additional output). The code produces (e.g.) the following output:
Provider : SunJCE
OAEP-Hash : SHA-256
MGF1-Hash : SHA-1
Ciphertext: WlozD9ojNRQafip41dpuuhBMe7ruH2FBWnMhbAaSuAtPDpHOUyKaAm6mO15BbvL3eTXyqfEQx29dYPJEbUr5T/WXs846PQN6g7Yv25EXGVbPCzc4aIbms76C1jP92wXNEGWMnu624Fq5W9MVXX75mfaY0Fjvrh5k/TFuO4AIxMk=
For completeness it should be mentioned that an explicit specification of the parameters is also possible with:
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);
whereby this explicit specification is the more robust alternative because of the ambivalence described above.
After the digests have been determined (either because the provider is known or explicitly with the above output) a Ruby implementation can be made.
A possible OAEP implementation for Ruby is openssl-oaep.
With this, the Ruby code for decryption can be implemented as follows:
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
with the original plaintext as output.