I am trying to implement an RSA based encryption communication between my client and server. For this I generated RSA public and private keys using openssl in the following way :
openssl genrsa -out private.pem 2048
openssl rsa -in private.pem -outform PEM -pubout -out public.pem
--generate modulus and exponent
openssl rsa -pubin -in public_key.pem -modulus -noout
openssl rsa -pubin -in public_key.pem -text -noout
Using the above files, encryption is done on the Android side as follows :
public static byte[] encrypt(BigInteger modulus, BigInteger exp, byte[] data) {
try {
KeyFactory kf = KeyFactory.getInstance("RSA");
RSAPublicKeySpec keySpec = new RSAPublicKeySpec(modulus, exp);
PublicKey publicKey = kf.generatePublic(keySpec);
final Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return cipher.doFinal(data);
} catch (Exception e) {
FileLog.e("module" , e);
}
return null;
}
This works fine and does encrypt the data. Now on the server side, I used the following code to decrypt. The private key is stored as a pkcs8 format key and that is read by java.
public static byte[] decrypt(byte[] data) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException{
String fileName = "location/of/private_key/file";
File f = new File(fileName);
FileInputStream fis = new FileInputStream(f);
DataInputStream dis = new DataInputStream(fis);
byte[] keyBytes = new byte[(int)f.length()];
dis.readFully(keyBytes);
dis.close();
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance("RSA");
PrivateKey rsaPrivate = kf.generatePrivate(spec);
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, rsaPrivate);
return cipher.doFinal(data);
}
On running this using eclipse on the server, I get the BadPaddingException: Decryption error problem. The length of the data is exactly 256 bytes and hence the length should not be an issue.
Any help would be truly helpful.
Thanks
Android using different padding algorithm. You need to use other algorithm like below:
Cipher CheckCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
There are quit number of posts out there.
You can refer
RSA BadPaddingException in Java - encrypt in Android decrypt in JRE
RSA Encryption: Difference between Java and Android
Related
I am able to encrypt the data in the file using below mentioned Java code. But when I try to decrypt the encrypted file using OpenSSL from the command line then I am not be able to do that.
I tried the command
openssl enc -aes-256-cbc -d -in file_05.encrypt.txt -out file_05.decrypt.txt
It asked for a password -
enter aes-256-cbc decryption password:
I entered the password as "helloworld",
Then in the terminal it shows a "bad magic number" error message.
String pwd = "helloworld";
String SALT_VALUE = "12345#salt";
private String algorithm = "PBEWITHSHA256AND256BITAES-CBC-BC";
String finalTxt = "Hi, Goof After noon All.";
char[] pwd = Crypt.password.toCharArray();
SecretKey originalKey = Crypt.generateSK(pwd);
byte[] cipherText = Crypt.encrypt(finalTxt.getBytes(),SALT_VALUE.getBytes(), originalKey);
public static SecretKey generateSK(char[] passPhrase) throws NoSuchAlgorithmException,
InvalidKeySpecException,
NoSuchPaddingException,
InvalidAlgorithmParameterException,
InvalidKeyException {
PBEKeySpec pbeKeySpec = new PBEKeySpec(passPhrase);
SecretKeyFactory secretKeyFactory;
secretKeyFactory = SecretKeyFactory.getInstance(algorithm);
return secretKeyFactory.generateSecret(pbeKeySpec);
}
public static byte[] encrypt(byte[] image, byte[] salt, SecretKey sKey) throws InvalidKeyException,
IllegalBlockSizeException,
BadPaddingException,
InvalidKeySpecException,
UnsupportedEncodingException,
InvalidAlgorithmParameterException {
Cipher cipher;
try {
cipher = getCipher(Cipher.ENCRYPT_MODE, salt, sKey);
return cipher.doFinal(image);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private static Cipher getCipher(int mode, #NonNull byte[] salt, #NonNull SecretKey secretKey) throws Exception {
PBEParameterSpec pbeParamSpecKey = new PBEParameterSpec(salt, 1000);
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(mode, secretKey, pbeParamSpecKey);
return cipher;
}
It asked for a password - enter aes-256-cbc decryption password: I entered the password as "helloworld",
Then in the terminal it shows a "bad magic number" error message
Openssl uses by default its internal EVP_BytesToKey function to generate key and IV from provided password and salt. Just search on internet to find Java implementation if needed.
By default Openssl expect format Salted__<8bit_salt><ciphertext> if you don't provide key and IV directly.
I try to decrypt the encrypted file using OpenSSL from the command line then I am not be able to do that
I am not sure what your Crypt class is implemented, you may try to print hex encoded key and iv. The using openssl with parameters -iv <hex_IV> -K <hex_key> you can directly provide the IV and Key value to decrypt the ciphertext
It seems like you are missing header expected by openssl - string Salted__, followed by 8 bytes salt, followed by ciphertext.
I am Signing some text using "Windows-MY" KeyStore .
I want to sign using my private key and verify using Public Key.
KeyStore keyStore = KeyStore.getInstance("Windows-MY");
keyStore.load(null, null);
Enumeration en = keyStore.aliases();
while (en.hasMoreElements()) {
KeyStore keyStore = KeyStore.getInstance("Windows-MY");
keyStore.load(null, null);
String alias = en.nextElement().toString();
X509Certificate c = (X509Certificate) keyStore.getCertificate(alias);
String serialNumber = c.getSerialNumber().toString();
System.out.println("--" + aliasName);
PrivateKey privateKey = (PrivateKey) keyStore.getKey(aliasName, null);
PublicKey publicKey = (PublicKey) c.getPublicKey();
Certificate[] chain = keyStore.getCertificateChain(aliasName);
DataOutputStream fout = new DataOutputStream(outstream);
// -------------------------------------------------------
String data = "Monika";
byte[] content = data.getBytes();
Provider p = keyStore.getProvider();
// ----------------------signature---start---------------------------
Signature signature = Signature.getInstance("SHA256withRSA", p);
System.out.println(" signature.getProvider():"+ signature.getProvider());
signature.initSign(privateKey);
signature.update(content);
byte[] signatureBytes = signature.sign();
System.out.println("signatureBytes-------------"+ signatureBytes.toString());
// ----------------------signature----------end------------------
// ------------------------verification---------------
Signature signature1 = Signature.getInstance("SHA256withRSA", p);
System.out.println(" signature1.getProvider():"+ signature1.getProvider());
signature1.initVerify(publicKey);
signature1.update(content);
boolean verifies = signature1.verify(signatureBytes);
System.out.println("signature verifies: " + verifies);
// ------------------------------------------------
fout.close();
} // while
Output:
privateKey:RSAPrivateKey [size=2048 bits, type=Exchange, container=AC0BEBA9-A361-4611-96D9-B365B671FBC3]
signature.getProvider():SunMSCAPI version 1.6
signatureBytes-------------[B#1402d5a
signature1.getProvider():SunRsaSign version 1.5
signature verifies: false
Notice that:
My Private key is already RSAPrivateKey .
Provider for Signing is SunMSCAPI.
But I dont know about Provider for Verification with PrivateKey.
There are several issues with your code:
You are simply using the first certificate / public key from your windows keystore. This might actually be the right one here, but there might be more than one certificate in the keystore and then it is just coincidence which certificate you are using for verify.
String alias = en.nextElement().toString();
X509Certificate c = (X509Certificate) keyStore.getCertificate(alias);
PublicKey publicKey = c.getPublicKey();
PrivateKey privateKey = (PrivateKey) keyStore.getKey(DSCName, null);
You should write keyStore.getCertificate(DSCName) instead to make sure it matches the private key.
You are generating a key (resp. trying to convert the existing key) for no reason. You can remove this code completely. This will also solve your problem with the NullPointerException:
byte[] encodedPrivateKey = privateKey.getEncoded();
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encodedPrivateKey);
RSAPrivateKey privateKey1 = (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
There is a lot of unnecessary code in your question, like loading the certificate chain, but never using it. That makes it harder to fix. A minimal (working) example would look like this:
String alias = "myAlias";
String myData = "data to encrypt";
KeyStore keyStore = KeyStore.getInstance("Windows-MY");
keyStore.load(null, null);
X509Certificate cert = (X509Certificate) keyStore.getCertificate(alias);
PrivateKey privateKey = (PrivateKey) keyStore.getKey(alias, null);
PublicKey publicKey = cert.getPublicKey();
Signature instance = Signature.getInstance("SHA256withRSA");
instance.initSign(privateKey, new SecureRandom());
instance.update(myData.getBytes("UTF-8"));
byte[] signedBytes = instance.sign();
instance.initVerify(publicKey);
instance.update(myData.getBytes("UTF-8"));
System.out.println(instance.verify(signedBytes));
Signature signature = Signature.getInstance("SHA256withRSA",p);
System.out.println(" signature.getProvider():"+ signature.getProvider());
signature.initSign(privateKey, new SecureRandom());
signature.update(byteData);
byte[] signatureBytes = signature.sign();
// X509Certificate cert1 =signatureBytes.
System.out.println("signatureBytes-------------"+ signatureBytes.toString());
// ----------------------signature----------end------------------
// ------------------------verification---------------
Signature signature1 = Signature.getInstance("SHA256withRSA");
System.out.println(" signature1.getProvider():"+ signature1.getProvider());
signature1.initVerify(publicKey);
signature1.update(byteData);
boolean verifies = signature1.verify(signatureBytes);
System.out.println("signature verifies: " + verifies);
You are getting null here
byte[] encodedPrivateKey = privateKey.getEncoded(); // are you sure that this byte array is not null ?
To make things more safe check here:
PrivateKey privateKey = (PrivateKey) keyStore.getKey(
DSCName, null); // this maybe returning null
so before the line which gives error, make a check:
if(encodedPrivateKey==null){
System.out.println("private key is null");
}
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(
encodedPrivateKey);
Check this complete and working code to verify signature after signing some text/data using digest SHA256withRSA:
/* verifyRSAsha256.java */
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.X509EncodedKeySpec;
import javax.xml.bind.DatatypeConverter;
/*
Compile:
clear && javac verifyRSAsha256.java && java verifyRSAsha256
Create private key:
openssl genrsa -des3 -out encrypted.pem 2048 && openssl rsa -in encrypted.pem -out private.pem -outform PEM && openssl rsa -in private.pem -pubout > public.pem
Create signature:
/bin/echo -n "some text that you want to be trusted" > data.txt
openssl dgst -sha256 -sign private.pem data.txt > signature.tmp
base64 signature.tmp
Verify signature:
openssl dgst -sha256 -verify public.pem -signature signature.tmp data.txt
*/
public class verifyRSAsha256 {
public static void main(String args[]){
String publicKey =
// "-----BEGIN PUBLIC KEY-----"+
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwns0JWYgEshlpLYsZQFc"+
"d5iVSqIHDO0zISLlO1aK4bbbosSvRE81+inKrG5mlnkIrv0+mJ/qTLY1RdBAVAe4"+
"GPLTpHmLJhEtq7stydm2cCTEPRwfJNjoHqATDHEm1KLVGA8k0hztfMr8fLChE3/K"+
"n2MHxzs7qhMLyBdPqbVC9RNja3i+Nl814xPTSXJ50zdJMLC56VtIU0xjqNjXN8iQ"+
"pLZ2EfcP55nZ/venD01yxfsUn4sQLFTAlXqygA10fdDv9y0eZvgaGGSb4MuPT7yD"+
"BfgNEU3tl4nRdSzPNkCkCmkuaa/pqZ5uw+G0HBwaQlHDwsnIcwE/xo6aHpt4xF4W"+
"/QIDAQAB";
// "-----END PUBLIC KEY-----";
byte[] signature, data;
// the signature is a binary data and I encoded it with base64, so the signature must be decoded from base64 to binary again
signature = DatatypeConverter.parseBase64Binary("Yy9CdQDfdYWwZkSu2SZgoFABHk5Bd3tzYvX73QR+GDCWpUsWrO5CXRF+j3dBz+bq1SRQ+1c1hdez5mMeE1587s4Mos8EsT1sqNemu4l4535P+jYicwG1m2MAesquAHhIIAyY9iGID576ehX0/34rCCeGuVZablpL+2ki6cEwxPVlH7xtWNIc1AdxivHjkWorkWC1LrbfcNdbZhUrNuz7DZsxHP2sr+2TQdD4L9CA2bgpj6HeQt+MTfCf2PKSdVoLFdwnM8638jHL6MVcEJxeIow/YUDZGEAyR743RdRk4CGU1bJ7er9M1Q4hFfYWGOBsLBok2XXUJcchLgWET1eKdA==");
// the signature length have to be 256 bytes
System.out.print("Signature length 256 = ");
System.out.println(signature.length);
// the data used the generate the signature
data = "some text that you want to be trusted".getBytes();
// verify if signature is ok
try {System.out.println(verify(data,signature,publicKey));}catch(GeneralSecurityException e){e.printStackTrace();}
// if any byte of data changes (ex: change last byte from d to D)
data = "some text that you want to be trusteD".getBytes();
// the signature doesn't math and method verify will fail
try {System.out.println(verify(data,signature,publicKey));}catch(GeneralSecurityException e){e.printStackTrace();}
}
private static boolean verify(byte[] data, byte[] signature, String publicKey) throws GeneralSecurityException{
X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(DatatypeConverter.parseBase64Binary(publicKey));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey pubKey = keyFactory.generatePublic(pubKeySpec);
Signature sig = Signature.getInstance("SHA256withRSA");
sig.initVerify(pubKey);
sig.update(data);
return sig.verify(signature);
}
}
You can create a private and public key using openssl command line tool:
openssl genrsa -des3 -out encrypted.pem 2048
openssl rsa -in encrypted.pem -out private.pem -outform PEM
openssl rsa -in private.pem -pubout > public.pem
You can create one signature with your private key using openssl command line tool:
/bin/echo -n "some text that you want to be trusted" > data.txt
openssl dgst -sha256 -sign private.pem data.txt > signature.tmp
You can verify if the signature is correct using openssl command line tool:
openssl dgst -sha256 -verify public.pem -signature signature.tmp data.txt
My knowledge of encryption is very basic, so apologies for any ignorance on my part.
Within an Android app I am currently trying to mimic the execution of this command using the SpongyCastle library and standard java.security libs:
echo 'test' | openssl rsautl -encrypt -pubin -inkey test.pub | base64 > encrypted_file
It should be noted that the reading/writing to and from files in the command are not going to be implemented and I have my public key (i.e. test.pub) as a Base64 encoded string base64key in my code.
I have attempted the following but am certain it does not work:
static {
Security.insertProviderAt(new BouncyCastleProvider(), 1);
}
//...more code here
byte[] pka = Base64.decode(base64key);
X509EncodedKeySpec x = new X509EncodedKeySpec(pka);
PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(x);
byte[] testToByte = "test".getBytes("UTF8");
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(2048);
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] cipherText = cipher.doFinal(testToByte);
String encrypted = Base64.encode((new String(cipherText, "UTF8").toString().getBytes()))
I know this is way off, but am not sure where to turn. Any help would be appreciated.
This was eventually solved using the following methods:
private void stripHeaders(){
public_key = public_key.replace("-----BEGIN PUBLIC KEY-----", "");
public_key = public_key.replace("-----END PUBLIC KEY-----", "");
}
public byte[] encryptWithPublicKey(String encrypt) throws Exception {
byte[] message = encrypt.getBytes("UTF-8");
stripHeaders();
PublicKey apiPublicKey= getRSAPublicKeyFromString();
Cipher rsaCipher = Cipher.getInstance("RSA/None/PKCS1Padding", "SC");
rsaCipher.init(Cipher.ENCRYPT_MODE, apiPublicKey);
return rsaCipher.doFinal(message);
}
private PublicKey getRSAPublicKeyFromString() throws Exception{
KeyFactory keyFactory = KeyFactory.getInstance("RSA", "SC");
byte[] publicKeyBytes = Base64.decode(public_key.getBytes("UTF-8"), Base64.DEFAULT);
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(publicKeyBytes);
return keyFactory.generatePublic(x509KeySpec);
}
I want to generate a RSA keypair in the Android Keystore. Since Android 4.3 is should be possible to generate RSA keys in the Android system Keystore.
I generate my RSA key by (works fine)
Calendar notBefore = Calendar.getInstance();
Calendar notAfter = Calendar.getInstance();
notAfter.add(1, Calendar.YEAR);
KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(ctx)
.setAlias("key")
.setSubject(
new X500Principal(String.format("CN=%s, OU=%s",
"key", ctx.getPackageName())))
.setSerialNumber(BigInteger.ONE)
.setStartDate(notBefore.getTime())
.setEndDate(notAfter.getTime()).build();
KeyPairGenerator kpg;
kpg = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
kpg.initialize(spec);
KeyPair kp = kpg.genKeyPair();
publicKey = kp.getPublic();
privateKey = kp.getPrivate();
my RSA encryption looks like (works also):
public static byte[] RSAEncrypt(final byte[] plain)
throws NoSuchAlgorithmException, NoSuchPaddingException,
InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
Cipher cipher = Cipher.getInstance("RSA");
System.out.println("RSA Encryption key: " + publicKey.getAlgorithm());
System.out.println("RSA Encryption key: " + publicKey.getEncoded());
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encryptedBytes = cipher.doFinal(plain);
return encryptedBytes;
}
decryption:
public static byte[] RSADecrypt(final byte[] encryptedBytes)
throws NoSuchAlgorithmException, NoSuchPaddingException,
InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
Cipher cipher1 = Cipher.getInstance("RSA");
System.out.println("RSA Encryption key: " + privateKey.getAlgorithm());
System.out.println("RSA Encryption key: " + privateKey.getEncoded());
cipher1.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decryptedBytes = cipher1.doFinal(encryptedBytes);
return decryptedBytes;
}
In the decryption function i get the following error message(when the privateKey is encoded, and in cipher1.init()):
12-12 21:49:40.338: E/AndroidRuntime(20423): FATAL EXCEPTION: main
12-12 21:49:40.338: E/AndroidRuntime(20423): java.lang.UnsupportedOperationException: private exponent cannot be extracted
12-12 21:49:40.338: E/AndroidRuntime(20423): at org.apache.harmony.xnet.provider.jsse.OpenSSLRSAPrivateKey.getPrivateExponent(OpenSSLRSAPrivateKey.java:143)
I don't get it. Is it not possible to generate a RSA key in the Android KeyStore? Can anyone provide me with an example of generating a RSA key in the Android KeyStore and decrypt with the private Key.
Many Thanks in advance!
According to the code, I think that the OpenSSL provider prevents the private exponent to be exported when the key has been generated into the device.
#Override
public final BigInteger getPrivateExponent() {
if (key.isEngineBased()) {
throw new UnsupportedOperationException("private exponent cannot be extracted");
}
ensureReadParams();
return privateExponent;
}
Thus, you probably need to specify that you want to use the same crypto provider when retrieving the cipher instance. This provider supports these RSA ciphers:
RSA/ECB/NoPadding
RSA/ECB/PKCS1Padding
You should create an instance of the cipher this way:
Cipher cipher1 = Cipher.getInstance("RSA/ECB/PKCS1Padding", "AndroidOpenSSL");
In my program, I'm trying to encrypt some plaintext with RSA using the following code:
static String RSAEncrypt(String pubkey, String plain){
return encrypt(pubkey,plain,"RSA");
}
static String encrypt(String stringKey, String plain, String algo){
String enc="failed";
try{
byte[] byteKey = new BASE64Decoder().decodeBuffer(stringKey);
Key key = new SecretKeySpec(byteKey,algo);
byte[] data = plain.getBytes();
Cipher c = Cipher.getInstance(algo);
c.init(Cipher.ENCRYPT_MODE, key);
byte[] encVal = c.doFinal(data);
enc = new BASE64Encoder().encode(encVal);
}catch(Exception e){e.printStackTrace();}
return enc;
}
However, when it runs, it shows the following error:
java.security.InvalidKeyException: No installed provider supports this key: javax.crypto.spec.SecretKeySpec
at javax.crypto.Cipher.chooseProvider(Cipher.java:877)
at javax.crypto.Cipher.init(Cipher.java:1212)
at javax.crypto.Cipher.init(Cipher.java:1152)
at Crypto.encrypt(Crypto.java:37)
at Crypto.RSAEncrypt(Crypto.java:62)
I have tried changing it to RSA/None/PKCS1Padding and RSA/ECB/PKCS1Padding to no avail.. I know that installing BouncyCastle may help but I'd like to avoid it (I'd like to avoid more dependencies and I've been having some issues installing it anyway). Thanks in advance for any ideas.
As was said in the comments, SecretKeySpec is for symmetric algorithms only. You mentioned that you got your byte[] containing the key by calling getEncoded.
There are two possibilities and two resulting formats:
Encoding of an RSA PrivateKey
Calling PrivateKey#getEncoded on an instance of an RSA private key will result in a PKCS#8 encoding for private keys, and it can be restored with the help of PKCS8EncodedKeySpec.
Encoding of an RSA PublicKey
PublicKey#getEncoded on an RSA public key results in the generic X.509 public key encoding, and can be restored with X509EncodedKeySpec.
Example Usage
byte[] data = "test".getBytes("UTF8");
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(512);
KeyPair keyPair = kpg.genKeyPair();
byte[] pk = keyPair.getPublic().getEncoded();
X509EncodedKeySpec spec = new X509EncodedKeySpec(pk);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey pubKey = keyFactory.generatePublic(spec);
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
byte[] encrypted = cipher.doFinal(data);
byte[] priv = keyPair.getPrivate().getEncoded();
PKCS8EncodedKeySpec spec2 = new PKCS8EncodedKeySpec(priv);
PrivateKey privKey = keyFactory.generatePrivate(spec2);
cipher.init(Cipher.DECRYPT_MODE, privKey);
byte[] plain = cipher.doFinal(encrypted);
System.out.println(new String(plain, "UTF8")); //=> "test"