tldr: generated rsa key pair using node-forge, encrypted hello and converted it to base64 in order to decrypt it using the same key pair (imported using pem files) in java but it throws an exception while the key pair is working fine with data encrypted by java
I am generating a key pair in node which works fine for decrypting and encrypting in both java and node, but I am unable to decrypt the encrypted value from node in java
the key pair is generated using the following nodejs code
var bip39 = require("bip39");
var forge = require("node-forge");
async function test() {
var mnemonic = "fever draw mind boil renew fire sleep palace useful ritual wood snake april polar brown off round zebra swallow health grass drink jelly beef";
console.log(mnemonic)
let seed = await bip39.mnemonicToSeed(mnemonic);
console.log(seed.toString("hex"))
const prng = forge.random.createInstance();
prng.seedFileSync = () => seed
const {
privateKey,
publicKey
} = forge.pki.rsa.generateKeyPair({
bits: 1024,
prng,
workers: 2
})
console.log(await forge.pki.publicKeyToRSAPublicKeyPem(publicKey));
console.log(await forge.pki.privateKeyToPem(privateKey));
let encrypted = publicKey.encrypt("hello");
var previousBase ="WTq03NoOtGHX Xs1Mu6bbQmjQ xSssj2mSeRf55 lw/tVaGRTI7KQZzu8DFqNJS4kzu6iJjqKTmnvtAMzPOo4KAmEtZAMDEZw7D1Y5ptb0cDP0SgROsZgZr5XpGbifQMDGT rZr/5EfFOXdwDvUy1rUABAKpSbXFZTF64UFM4lk=";
var previous = atob(previousBase);
let base = btoa(encrypted);
console.log(base);
var x = new TextEncoder().encode(previousBase);
var all = "";
for (var i = 0; i < x.length; i ) {
all = x[i] ", ";
}
console.log(all.substring(0, all.length - 2))
console.log(await privateKey.decrypt(previous));
console.log(await privateKey.decrypt(encrypted));
};
test();
since I am trying to decrypt/encrypt using the same private/public key in Java I decided to add a few logs so I have a base to work with
fever draw mind boil renew fire sleep palace useful ritual wood snake april polar brown off round zebra swallow health grass drink jelly beef
8f445e53ea42708dd4be30c646261c9fc3352125c42f8f5ead86d44c15adbac4a46ffe1dca758f99bf46c7b939fd3db0f2f3000b792bc9192156e5ef5936a732
-----BEGIN RSA PUBLIC KEY-----
MIGJAoGBAK0HXXtavoJkYXYSBFvb JINZ9MFZANLWRVOu69iiP6PgILqEbV8ybFm
vdSCuZu0BB/P5f0uW/0fw0149UZxXltrkPZHqYLPckXOxgRD3LAWbI vwV0UqU0X
597hg5E8UnV 2S5VG4/QxpAS5iiAPwT96dxjxEhZ3mRqLX74BsrDAgMBAAE=
-----END RSA PUBLIC KEY-----
-----BEGIN RSA PRIVATE KEY-----
MIICWwIBAAKBgQCtB117Wr6CZGF2EgRb2/iSDWfTBWQDS1kVTruvYoj j4CC6hG1
fMmxZr3UgrmbtAQfz X9Llv9H8NNePVGcV5ba5D2R6mCz3JFzsYEQ9ywFmyPr8Fd
FKlNF fe4YORPFJ1ftkuVRuP0MaQEuYogD8E/encY8RIWd5kai1 AbKwwIDAQAB
AoGAFDPb8l8yBz95MbQA1kjkyQjPqo/ikY/motpCh8PVgwN2WdLCppIfaps4Zuus
iEWIhb5ceCdFjlR7FTyeRs9N2OTC2Mcp1n1sxfHNFMGPOsxyMAaIw9UhFzpr2cHy
PXiPbyg y4EMwEbB2js7Zf2A2wNSda4VBkYUZIkBXIxmUwECQQD9a70jjdfN6Ldj
M6ZqPPg/V5GGsEohfsmsSJKUxzZ3AmFqrwzDmclbfviVmqTiULPDbl4Tb6qvhLc3
Qk//DUijAkEArsosXFv7DkZmMA3MTE8lURgOD509Ts1 4wWIDsbP54pvFoIPX7eG
6NR48SEvjAmZfGgwi6gbwUdBbRFFQwP3YQJAfuPmdZn9V5XR1XM0PXe/2X QV3 H
7tOcSY6hDqvdIqPngVKbMombYBvofohLTSKZkB6ALn04WuA6GQo0IgJVvwJARvsH
JMKdo2BnYyBXVK3XY6U3IJQkL3o4Cw1WAVovV8HZ9vP NkqbWLXgH1vwqRfE4saU
4EH2c3jyUs5uqBZWQQJAajdCCVRyPghnfqhzWl68gfvrEATDTWRt4UKY46IP QUx
islQ2e9ZU wbjBmqM5zd1oG9yUsxD8c5X0mCyZ1wSg==
-----END RSA PRIVATE KEY-----
HWqv4dvDtLPc8ZhBbTI4JJ89XgxASKacZKuaz 0HF0kInt9R/tDgv2lkNPl7RWC/SNacqY0DnBL1uAQvg8r75nVo7sZ29hhjph5n5ucBNXcT9OSYgGY4fjSmMLvLY3qVSYItSLsIbXrfemWDF2jaV2FbDb4dBCaeC02PjIJQGnw=
87, 84, 113, 48, 51, 78, 111, 79, 116, 71, 72, 88, 43, 88, 115, 49, 77, 117, 54, 98, 98, 81, 109, 106, 81, 43, 120, 83, 115, 115, 106, 50, 109, 83, 101, 82, 102, 53, 53, 43, 108, 119, 47, 116, 86, 97, 71, 82, 84, 73, 55, 75, 81, 90, 122, 117, 56, 68, 70, 113, 78, 74, 83, 52, 107, 122, 117, 54, 105, 74, 106, 113, 75, 84, 109, 110, 118, 116, 65, 77, 122, 80, 79, 111, 52, 75, 65, 109, 69, 116, 90, 65, 77, 68, 69, 90, 119, 55, 68, 49, 89, 53, 112, 116, 98, 48, 99, 68, 80, 48, 83, 103, 82, 79, 115, 90, 103, 90, 114, 53, 88, 112, 71, 98, 105, 102, 81, 77, 68, 71, 84, 43, 114, 90, 114, 47, 53, 69, 102, 70, 79, 88, 100, 119, 68, 118, 85, 121, 49, 114, 85, 65, 66, 65, 75, 112, 83, 98, 88, 70, 90, 84, 70, 54, 52, 85, 70, 77, 52, 108, 107, 61
hello
hello
the first challenge was to actually get the rsa key objects in Java to initialize without any exceptions, which in fact required me to add the dependency
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.70</version>
</dependency>
now within java I am able to encrypt and decrypt using the keys, but I am unable to decrypt anything that was generated within nodejs as it throws this exception
javax.crypto.IllegalBlockSizeException: Data must not be longer than 128 bytes
at com.sun.crypto.provider.RSACipher.doFinal(RSACipher.java:344)
at com.sun.crypto.provider.RSACipher.engineDoFinal(RSACipher.java:389)
at javax.crypto.Cipher.doFinal(Cipher.java:2164)
at Test.main(Test.java:44)
my current code for reference looks like this
import javax.crypto.Cipher;
import javax.xml.bind.DatatypeConverter;
import java.io.File;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
public class Test {
static {
java.security.Security.addProvider(
new org.bouncycastle.jce.provider.BouncyCastleProvider()
);
}
public static void main(String[] args) {
try {
RSAPrivateKey privateKey = parsePrivateKey(Files.readAllBytes(Paths.get("pri.pem")));
RSAPublicKey pub = readPublicKey(new File("pub.pem"));
Cipher encryptCipher = Cipher.getInstance("RSA");
encryptCipher.init(Cipher.ENCRYPT_MODE, pub);
String test = "hello";
byte[] testBytes = test.getBytes(StandardCharsets.UTF_8);
byte[] encryptedMessageBytes = encryptCipher.doFinal(testBytes);
String base64 = "WTq03NoOtGHX Xs1Mu6bbQmjQ xSssj2mSeRf55 lw/tVaGRTI7KQZzu8DFqNJS4kzu6iJjqKTmnvtAMzPOo4KAmEtZAMDEZw7D1Y5ptb0cDP0SgROsZgZr5XpGbifQMDGT rZr/5EfFOXdwDvUy1rUABAKpSbXFZTF64UFM4lk=";
byte[] decodedBytes = Base64.getDecoder().decode(base64);
String decodedString = new String(decodedBytes);
byte[] secretMessageBytes = decodedString.getBytes(StandardCharsets.UTF_8.name());
Cipher decryptCipher = Cipher.getInstance("RSA");
decryptCipher.init(Cipher.DECRYPT_MODE, privateKey);
System.out.println(new String(decryptCipher.doFinal(encryptedMessageBytes)));
byte[] decryptedMessageBytes = decryptCipher.doFinal(secretMessageBytes);
System.out.println(new String(decryptedMessageBytes));
} catch (Exception e) {
e.printStackTrace();
}
}
public static RSAPrivateKey parsePrivateKey(byte[] b) throws InvalidKeySpecException, NoSuchAlgorithmException {
byte[] privateKeyBytes = parseDERFromPEM(b);
return generatePrivateKeyFromDER(privateKeyBytes);
}
public static byte[] parseDERFromPEM(byte[] b) {
return DatatypeConverter.parseBase64Binary(parseKeyData(b));
}
public static String parseKeyData(byte[] b) {
String data = new String(b);
String[] tokens = data.split("-----BEGIN RSA PRIVATE KEY-----");
tokens = tokens[1].split("-----END RSA PRIVATE KEY-----");
return tokens[0];
}
public static RSAPrivateKey generatePrivateKeyFromDER(byte[] b) throws InvalidKeySpecException, NoSuchAlgorithmException {
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(b);
KeyFactory factory = KeyFactory.getInstance("RSA");
return (RSAPrivateKey) factory.generatePrivate(spec);
}
public static RSAPublicKey readPublicKey(File file) throws Exception {
String key = new String(Files.readAllBytes(file.toPath()), Charset.defaultCharset());
String publicKeyPEM = key
.replace("-----BEGIN PUBLIC KEY-----", "")
.replaceAll(System.lineSeparator(), "")
.replace("-----END PUBLIC KEY-----", "");
byte[] encoded = Base64.getDecoder().decode(publicKeyPEM);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encoded);
return (RSAPublicKey) keyFactory.generatePublic(keySpec);
}
}
is it somehow impossible todo what I am trying todo or am I just missing something somewhere since the keys are working with java for encryption and decryption as long as it was encrypted using java.
I believe all data required to replicate my issue is included in this post - if any further information is needed please let me know and I will provide
CodePudding user response:
Encrypted data using modern (computer) ciphers is not text. And the data that can be encrypted can be any bits, not just text, although they are still called 'ciphertext' and 'plaintext' following a historical tradition from past centuries where they actually were text. Neither is other cryptographic data, such as digital signatures, hashes, keys, certificates, and such, although some of them are converted to and from text forms such as (several variants of) base64, hex, and PEM. Your node/forge code represents the RSA-encrypted data in base64, as is common to handle this, but your Java code after decoding the base64 correctly does the following
String decodedString = new String(decodedBytes);
byte[] secretMessageBytes = decodedString.getBytes(StandardCharsets.UTF_8.name());
which tries to treat it as text. This does not work, and turns much of your data into garbage, which also happens to make it longer and trigger the exception you got. You need to use the result of the base64 decoding directly in the decrypt call.
In addition your posted Java code doesn't and can't work on the public-key format you posted. You need to either use forge's publicKeyToPem
(which produces the correct 'generic' PUBLIC KEY format -- not RSA PUBLIC KEY) or convert your posted output with something like openssl rsa -RSAPublicKey_in
; I did the latter.
Finally, it isn't sufficient to just add Bouncy to the provider list. You must either:
- specify the actual provider object in
KeyFactory.getInstance
- have Bouncy in the list (anywhere) and specify "BC" in
KeyFactory.getInstance
- put Bouncy at the beginning of the list with
insertProviderAt(new org.bouncy...,1)
With those three changes, and some trivial simplification and hardcoded data for convenience, the following gives hello
as expected:
String pubbad = "-----BEGIN RSA PUBLIC KEY-----\n"
"MIGJAoGBAK0HXXtavoJkYXYSBFvb JINZ9MFZANLWRVOu69iiP6PgILqEbV8ybFm\n"
"vdSCuZu0BB/P5f0uW/0fw0149UZxXltrkPZHqYLPckXOxgRD3LAWbI vwV0UqU0X\n"
"597hg5E8UnV 2S5VG4/QxpAS5iiAPwT96dxjxEhZ3mRqLX74BsrDAgMBAAE=\n"
"-----END RSA PUBLIC KEY-----\n";
String pubfix = "-----BEGIN PUBLIC KEY-----\n"
"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCtB117Wr6CZGF2EgRb2/iSDWfT\n"
"BWQDS1kVTruvYoj j4CC6hG1fMmxZr3UgrmbtAQfz X9Llv9H8NNePVGcV5ba5D2\n"
"R6mCz3JFzsYEQ9ywFmyPr8FdFKlNF fe4YORPFJ1ftkuVRuP0MaQEuYogD8E/enc\n"
"Y8RIWd5kai1 AbKwwIDAQAB\n"
"-----END PUBLIC KEY-----\n";
String prvpem = "-----BEGIN RSA PRIVATE KEY-----\n"
"MIICWwIBAAKBgQCtB117Wr6CZGF2EgRb2/iSDWfTBWQDS1kVTruvYoj j4CC6hG1\n"
"fMmxZr3UgrmbtAQfz X9Llv9H8NNePVGcV5ba5D2R6mCz3JFzsYEQ9ywFmyPr8Fd\n"
"FKlNF fe4YORPFJ1ftkuVRuP0MaQEuYogD8E/encY8RIWd5kai1 AbKwwIDAQAB\n"
"AoGAFDPb8l8yBz95MbQA1kjkyQjPqo/ikY/motpCh8PVgwN2WdLCppIfaps4Zuus\n"
"iEWIhb5ceCdFjlR7FTyeRs9N2OTC2Mcp1n1sxfHNFMGPOsxyMAaIw9UhFzpr2cHy\n"
"PXiPbyg y4EMwEbB2js7Zf2A2wNSda4VBkYUZIkBXIxmUwECQQD9a70jjdfN6Ldj\n"
"M6ZqPPg/V5GGsEohfsmsSJKUxzZ3AmFqrwzDmclbfviVmqTiULPDbl4Tb6qvhLc3\n"
"Qk//DUijAkEArsosXFv7DkZmMA3MTE8lURgOD509Ts1 4wWIDsbP54pvFoIPX7eG\n"
"6NR48SEvjAmZfGgwi6gbwUdBbRFFQwP3YQJAfuPmdZn9V5XR1XM0PXe/2X QV3 H\n"
"7tOcSY6hDqvdIqPngVKbMombYBvofohLTSKZkB6ALn04WuA6GQo0IgJVvwJARvsH\n"
"JMKdo2BnYyBXVK3XY6U3IJQkL3o4Cw1WAVovV8HZ9vP NkqbWLXgH1vwqRfE4saU\n"
"4EH2c3jyUs5uqBZWQQJAajdCCVRyPghnfqhzWl68gfvrEATDTWRt4UKY46IP QUx\n"
"islQ2e9ZU wbjBmqM5zd1oG9yUsxD8c5X0mCyZ1wSg==\n"
"-----END RSA PRIVATE KEY-----\n";
//RSAPrivateKey privateKey = parsePrivateKey(Files.readAllBytes(Paths.get("pri.pem")));
byte[] prvder = Base64.getDecoder().decode( prvpem.replaceAll("-----(BEGIN|END) RSA PRIVATE KEY-----","").replaceAll("\n","") );
RSAPrivateKey privateKey = (RSAPrivateKey) KeyFactory.getInstance("RSA","BC") .generatePrivate(new PKCS8EncodedKeySpec(prvder) );
// actually not PKCS8, but BCprov handles this as a special case
//RSAPublicKey pub = readPublicKey(new File("pub.pem"));
byte[] pubder = Base64.getDecoder().decode( pubfix.replaceAll("-----(BEGIN|END) PUBLIC KEY-----","").replaceAll("\n","") );
RSAPublicKey pub = (RSAPublicKey) KeyFactory.getInstance("RSA") .generatePublic(new X509EncodedKeySpec(pubder) );
String base64 = "WTq03NoOtGHX Xs1Mu6bbQmjQ xSssj2mSeRf55 lw/tVaGRTI7KQZzu8DFqNJS4kzu6iJjqKTmnvtAMzPOo4KAmEtZAMDEZw7D1Y5ptb0cDP0SgROsZgZr5XpGbifQMDGT rZr/5EfFOXdwDvUy1rUABAKpSbXFZTF64UFM4lk=";
byte[] decodedBytes = Base64.getDecoder().decode(base64);
//BAD String decodedString = new String(decodedBytes);
//BAD byte[] secretMessageBytes = decodedString.getBytes(StandardCharsets.UTF_8.name());
Cipher decryptCipher = Cipher.getInstance("RSA");
decryptCipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decryptedMessageBytes = decryptCipher.doFinal(decodedBytes);
System.out.println(new String(decryptedMessageBytes));
And merry Xmas, until someone edits this out because Stack is supposed to be impersonal.