I am trying to set up RSA Encryption in my Java project. I generated an asymmetric key with the following command in the Terminal:
keytool -genkey -keyalg RSA -alias mykey -keystore mykey.jks -storepass mykeypass -keypass mykeypass
Now I load the keystore with the following method:
public void loadKeyStore() throws KeyStoreException, CertificateException, IOException,
NoSuchAlgorithmException {
keyStore = KeyStore.getInstance(KEY_TYPE);
char[] storePwdArray = STORE_PASS.toCharArray();
FileInputStream fis = new FileInputStream(KEY_STORE_PATH);
keyStore.load(fis, storePwdArray);
}
Now, I have two methods, one for encryption, one for decryption:
public String encrypt(String data) throws CertificateException, KeyStoreException, IOException, NoSuchAlgorithmException, InvalidKeyException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException {
if (keyStore == null) {
loadKeyStore();
}
Certificate cert = keyStore.getCertificate(ALIAS);
Cipher rsa = Cipher.getInstance("RSA/ECB/PKCS1Padding");
rsa.init(Cipher.ENCRYPT_MODE, cert.getPublicKey());
byte[] encryptedBytes = rsa.doFinal(data.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encryptedBytes);
}
public String decrypt(String encryptedData) throws UnrecoverableKeyException, CertificateException, KeyStoreException, IOException, NoSuchAlgorithmException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchPaddingException {
if (keyStore == null) {
loadKeyStore();
}
Cipher rsa = Cipher.getInstance("RSA/ECB/PKCS1Padding");
char[] keyPwdArray = KEY_PASS.toCharArray();
Key key = keyStore.getKey(ALIAS, keyPwdArray);
rsa.init(Cipher.DECRYPT_MODE, key);
byte[] decryptedBytes = rsa.doFinal(encryptedData.getBytes());
return new String(decryptedBytes, StandardCharsets.UTF_8);
}
In the main method I try to encrypt and decrypt a String and print it, like that:
public static void main(String[] args) throws UnrecoverableKeyException, NoSuchPaddingException, IllegalBlockSizeException, CertificateException, KeyStoreException, IOException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
RSAEncryptionService encryptionService = new RSAEncryptionService();
String secretMessage = "Hello World!";
String encryptedMessage = encryptionService.encrypt(secretMessage);
System.out.println(encryptedMessage);
String decryptedMessage = encryptionService.decrypt(encryptedMessage);
System.out.println(decryptedMessage);
}
The encryption works fine and returns something like that: B61g7zzXDNW9AO/Idc/OBZOCDOJpQTwgchD9uJisEBgxy8HV1XPYZZaLEnxkJHed2sBAQXEIyCDcIAHWk5rxn40tVd4NwlIUya1rB6WNvRFLrrN30G7VjMU6NNUdwJ55n7is2Ylfu0SkwNpy/o4e9LaZyzCyr4lJsTbFEXJQJKqLsOC+ysHYdhzx61Y8UJw6mUhleju7h11OcdDBdGEtAtBcKx9WDt2cgHrdtYUgUkwmEy3vTuuyUwVVpjA4QwUsjXnN+i19FQBZt67sMYIpUT4x4yJ8egqN4mJ2N8aNLwF7m/FS7EZphXdna4KN0srKBbPquB1ER5be6RnoyMFDsg==
But when it comes to the decryption, I get the following Exception:
Exception in thread "main" javax.crypto.IllegalBlockSizeException: Data must not be longer than 256 bytes
at java.base/com.sun.crypto.provider.RSACipher.doFinal(RSACipher.java:349)
at java.base/com.sun.crypto.provider.RSACipher.engineDoFinal(RSACipher.java:406)
at java.base/javax.crypto.Cipher.doFinal(Cipher.java:2205)
What I tried:
I read somewhere on StackOverflow that I need to increase the size of the key. But, that also produces a longer encrypted String, and then the Exception just states "Data must not be longer than 512/1024/... bytes".
I tried using a KeyPair generated in Code, like below. That worked, but I have no idea how to get that KeyPair into my keystore.
public void initKeyStore() throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, KeyStoreException, IOException, CertificateException {
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
generator.initialize(2048);
KeyPair pair = generator.generateKeyPair();
Cipher encryptCipher = Cipher.getInstance("RSA");
encryptCipher.init(Cipher.ENCRYPT_MODE, pair.getPublic());
byte[] secretMessageBytes = "secretMessage".getBytes(StandardCharsets.UTF_8);
byte[] encryptedMessageBytes = encryptCipher.doFinal(secretMessageBytes);
String encodedMessage = Base64.getEncoder().encodeToString(encryptedMessageBytes);
System.out.println(encodedMessage);
Cipher decryptCipher = Cipher.getInstance("RSA");
decryptCipher.init(Cipher.DECRYPT_MODE, pair.getPrivate());
byte[] decryptedMessageBytes = decryptCipher.doFinal(encryptedMessageBytes);
String decryptedMessage = new String(decryptedMessageBytes, StandardCharsets.UTF_8);
System.out.println(decryptedMessage);
}
The reason is simple. You are using a 2048 bit certificate. it cannot encrypt any data bigger than 256 bytes because it need to be smaller than RSA modulus in case no RSA padding is used. If RSA-OEAP padding is used, another 11 bytes will be used for padding.
In RSA encryption, you normally need to use key encapsulation to support encryption of any arbitrary data sizes. In nutshell, it means that you need to encrypt you data with a random symmetric key and symmetric algorithm(like AES), and then encrypt its key using RSA and send both to other side. Other side first decrypt symmetric key, and then use it to decrypt original data. So it will be something like this:
Encryption side
-----------------
1- Generate random AES key
2- Encrypt data with AES and generated key
3- Encrypt Key with RSA public key
4- Send 2 encrypted data to the other side
Decryption side
-----------------
1- Split received data to encrypted key and encrypted data
1- Decrypt AES Key with RSA private key
2- Decrypt data with AES and decrypted AES key
In addition, read comment on this answer for further reading about padding and in detail implementations.
Related
I have created following to method
public static PublicKey readPublicKey(String filename) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
PublicKey key = null;
CertificateFactory fact;
try {
// MBFS certificate to be used
FileInputStream is = new FileInputStream(filename);
fact = CertificateFactory.getInstance("X.509");
System.out.println(is.toString());
X509Certificate cer = (X509Certificate) fact.generateCertificate(is);
key = cer.getPublicKey();
System.out.println(key.getAlgorithm());
} catch (CertificateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return key;
}
for encryption
public static byte[] encrypt(PublicKey key, byte[] plaintext) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, key);
return cipher.doFinal(plaintext);
}
I have long xml string and using these both method as following
byte[] message = xmlMessage.getBytes();
byte[] secret = encrypt(publicKey, message);
But it is giving me Data must not be longer than 256 bytes when using rsa
Certificate is shard by client it is saying Signature algorithm sha256RS.
Typically, you would use a symmetric cipher to encrypt the document (with a random secret key) and then just encrypt the key with RSA. This does not only overcome the length problem but is also much faster.
I'm trying to encrypt data with RSA public key in Java and decrypt it in Crypto++. It results in an error:
"RSA/EME-PKCS1-v1_5: ciphertext length of 24 doesn't match the required length of 128 for this key"
What am I doing wrong?
Java:
String cipher = Encryption.encryptStrRSA(txt, pubKeyk);
public static String encryptStrRSA(String str, PublicKey pubKey)
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
IllegalBlockSizeException, BadPaddingException, NoSuchProviderException {
Cipher cipher = Cipher.getInstance("RSA/NONE/PKCS1Padding", "BC");
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
byte[] encryptedAesKey = cipher.doFinal(str.getBytes());
String cipherStr = new String(encryptedAesKey);
System.out.println(cipherStr);
return cipherStr;
}
public static PublicKey strToPublicKey(String key64) throws GeneralSecurityException {
byte[] data = Base64.getDecoder().decode(key64);
X509EncodedKeySpec spec = new X509EncodedKeySpec(data);
KeyFactory fact = KeyFactory.getInstance("RSA");
return fact.generatePublic(spec);
}
public static String publicKeyToStr(PublicKey publ) throws GeneralSecurityException {
KeyFactory fact = KeyFactory.getInstance("RSA");
X509EncodedKeySpec spec = fact.getKeySpec(publ, X509EncodedKeySpec.class);
return Base64.getEncoder().encode(spec.getEncoded()).toString();
}
Crypto++:
using namespace CryptoPP;
RSAES_PKCS1v15_Decryptor priv(privString);
StringSource( cipher, cipherSize, true, new
Base64Decoder( new PK_DecryptorFilter(randPool, priv, new StringSink(sdata))));
It is dangerous to use String instances for keeping binary data -- you should use byte[] instead.
Additionally, in the java code there is no Base64 wrapping of the resulting ciphertext, but in the C++ code it is being unwrapped from Base64.
Modified your code to return byte[] and encode the result using Base64:
public static byte[] encryptRSA(String str, PublicKey pubKey) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchProviderException {
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", "BC");
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
return cipher.doFinal(str.getBytes());
}
String cipher = Base64.getEncoder().encodeToString(Encryption.encryptRSA("0123456789ABCDEF", pubKeyk));
Then you can decrypt in Crypto++ the same way you did.
Good luck!
I am developing an application that needs to validate SHA256withECDSAsignatures with the help of secp256r1 (NIST P-256, P-256, prime256v1) public keys.
The public keys are generated by a different application at some earlier point in time and stored in my database in hex encoding. The format of the hex string here is equivalent to the hex string OpenSSL would generate when calling openssl ec -in x.pem -noout -text on a file x.pem that has previously been generated by openssl ecparam -genkey -name secp256r1 -out x.pem.
The message and signature are received from a different application.
Consider the following test data:
// Stored in Database
byte[] pubKey = DatatypeConverter.parseHexBinary("049a55ad1e210cd113457ccd3465b930c9e7ade5e760ef64b63142dad43a308ed08e2d85632e8ff0322d3c7fda14409eafdc4c5b8ee0882fe885c92e3789c36a7a");
// Received from Other Application
byte[] message = DatatypeConverter.parseHexBinary("54686973206973206a75737420736f6d6520706f696e746c6573732064756d6d7920737472696e672e205468616e6b7320616e7977617920666f722074616b696e67207468652074696d6520746f206465636f6465206974203b2d29");
byte[] signature = DatatypeConverter.parseHexBinary("304402205fef461a4714a18a5ca6dce6d5ab8604f09f3899313a28ab430eb9860f8be9d602203c8d36446be85383af3f2e8630f40c4172543322b5e8973e03fff2309755e654");
Now this should be a valid signature.
My objective is to validate the signature over the message using the Java and/or Bouncycastle crypto API. I have created a method isValidSignaturefor that:
private static boolean isValidSignature(byte[] pubKey, byte[] message,
byte[] signature) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException, SignatureException, InvalidKeySpecException {
Signature ecdsaVerify = Signature.getInstance("SHA256withECDSA", new BouncyCastleProvider());
ecdsaVerify.initVerify(getPublicKeyFromHex(pubKey));
ecdsaVerify.update(message);
return ecdsaVerify.verify(signature);
}
I have tried to extract the public key:
KeyFactory.generatePublic:
private static PublicKey getPublicKeyFromHex(byte[] pubKey) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException {
KeyFactory fact = KeyFactory.getInstance("ECDSA", new BouncyCastleProvider());
return fact.generatePublic(new X509EncodedKeySpec(pubKey));
}
But this throws a java.security.spec.InvalidKeySpecException (DER length more than 4 bytes: 26).
What can I do to parse this?
The Bouncy Castle example code on elliptic curve key pair Generation and key factories got me pretty close.
Once I managed to create a ECDSA key factory and a curve specification for the secp256r1/NIST P-256/P-256/prime256v1 curve I was able to use ECPointUtil.decodePoint to obtain a curve point. I could then generate a public key specification that enabled me to generate a public key like this:
private PublicKey getPublicKeyFromBytes(byte[] pubKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("prime256v1");
KeyFactory kf = KeyFactory.getInstance("ECDSA", new BouncyCastleProvider());
ECNamedCurveSpec params = new ECNamedCurveSpec("prime256v1", spec.getCurve(), spec.getG(), spec.getN());
ECPoint point = ECPointUtil.decodePoint(params.getCurve(), pubKey);
ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(point, params);
ECPublicKey pk = (ECPublicKey) kf.generatePublic(pubKeySpec);
return pk;
}
first I generate a private RSA keyfile with OpenSSL and then convert it into an encrypted "der" file:
$ openssl pkcs8 -topk8 -inform PEM -outform DER -in private_key.pem -out private_key.der
Next I try to decrypt this file from Java using the following code(at this stage I already read the file into the byte[] key array using code that is at the bottom of this post):
public static byte[] decryptPrivateKey(byte[] key) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
PBEKeySpec passKeySpec = new PBEKeySpec("p".toCharArray()); //my password
EncryptedPrivateKeyInfo encryptedKey = new EncryptedPrivateKeyInfo(key);
System.out.println(encryptedKey.getAlgName());
//PBEWithMD5AndDES
System.out.println("key length: " + key.length);
//key length: 677
SecretKeyFactory keyFac = SecretKeyFactory.getInstance(encryptedKey.getAlgName());
SecretKey passKey = keyFac.generateSecret(passKeySpec);
// Create PBE Cipher
Cipher pbeCipher = Cipher.getInstance(encryptedKey.getAlgName());
// Initialize PBE Cipher with key and parameters
pbeCipher.init(Cipher.DECRYPT_MODE, passKey, encryptedKey.getAlgParameters());
// Decrypt the private key(throws the exception)
return pbeCipher.doFinal(key);
}
I get the following stack trace on the return statement above:
Exception in thread "main" javax.crypto.IllegalBlockSizeException: Input length must be multiple of 8 when decrypting with padded cipher
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:750)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:676)
at com.sun.crypto.provider.PBECipherCore.doFinal(PBECipherCore.java:422)
at com.sun.crypto.provider.PBEWithMD5AndDESCipher.engineDoFinal(PBEWithMD5AndDESCipher.java:316)
at javax.crypto.Cipher.doFinal(Cipher.java:2087)
at roland.test.crypto.Test.decryptPrivateKey(Test.java:96)
at roland.test.crypto.Test.getPrivateKey(Test.java:74)
at roland.test.crypto.Test.test(Test.java:58)
at roland.test.crypto.Test.main(Test.java:30)
The key is read from the "der" file as a byte array:
public static PrivateKey getPrivateKey() throws Exception {
byte[] key = null;
try(final InputStream resourceStream = getMyClass().getResourceAsStream("private_key.der")) { //$NON-NLS-1$\r
key = ByteStreams.toByteArray(resourceStream);
} catch (IOException e) {
e.printStackTrace();
}
key = decryptPrivateKey(key);
}
The solution is:
return pbeCipher.doFinal(encryptedKey.getEncryptedData());
I'm trying to encrypt using the loaded des key from KeyStore and I get:
Exception in thread "main" java.security.InvalidKeyException: No installed provider supports this key: sun.security.pkcs11.P11Key$P11SecretKey
at javax.crypto.Cipher.chooseProvider(Cipher.java:878)
at javax.crypto.Cipher.init(Cipher.java:1213)
at javax.crypto.Cipher.init(Cipher.java:1153)
and this is my code:
public static void main(String[] args) throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException, IllegalBlockSizeException, InvalidKeyException, BadPaddingException, NoSuchPaddingException, IOException, CertificateException {
Provider provider = new sun.security.pkcs11.SunPKCS11(DesSaveLoad.class.getClassLoader().getResourceAsStream("pkcs11.cfg"));
Security.removeProvider(provider.getName());
Security.insertProviderAt(provider, 1);
KeyStore keyStore = KeyStore.getInstance("PKCS11", provider);
keyStore.load(null, null);
SecretKey desKey = desGenerateKey();
keyStore.setKeyEntry("t1", desKey, null, null);
SecretKey t1 = (SecretKey) keyStore.getKey("t1", null);
byte[] messageBytes = "message".getBytes();
desEncrypt(messageBytes, 0, messageBytes.length, desKey);
desEncrypt(messageBytes, 0, messageBytes.length, t1); //Exception is thrown here
}
public static SecretKey desGenerateKey() throws NoSuchAlgorithmException {
KeyGenerator keygenerator = null;
keygenerator = KeyGenerator.getInstance("DES");
return keygenerator.generateKey();
}
public static byte[] desEncrypt(byte[] plainText, int offset, int size, SecretKey key) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
Cipher cipher;
if (size % 8 != 0) {
cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
} else {
cipher = Cipher.getInstance("DES/ECB/NoPadding");
}
cipher.init(Cipher.ENCRYPT_MODE, key);
return cipher.doFinal(plainText, offset, size);
}
As you can see there is no exception thrown when encrypting using generated des key.
If you perform encryption using a HSM then the encryption procedure is performed within the HSM, not in the software. Cipher does not implement the encryption procedure itself. The underlying CipherSpi of the PKCS#11 provider for Cipher is chosen using delayed provider selection depending on the key given during the call to init(). So although the desEncrypt() function seems to perform the same operations, in reality the functionality depends on the provider, and in your case, on the PKCS#11 wrapper, library and of course HSM.
Now PKCS#11 is an interface specification; not all mechanisms in PKCS#11 will be implemented in every token. It is likely that some encryption algorithms are too obscure or too unsafe. The latter is probably the case for DES ECB as that algorithm is extremely insecure. That does not mean that DES keys cannot be used in general - they could still play a role in e.g. MAC calculations. So please check the documentation of your HSM if DES ECB is supported (in the current setting).
You can get more information about the PKCS#11 method calls by adding -Djava.security.debug=sunpkcs11 to your call to the Java interpreter (java or javaw). If DES does not work, try the much safer and more common "AES/CBC/PKCS5Padding" or triple DES mechanism.
See if this post helps
Either the key is incorrect (more likely) or the given key is not supported by the provider.
KeyStore.getInstance("PKCS11", provider);
PS: Are you using a custom provider?