Android N InvalidKeyException - java

I have an app that is still targeting Android 6.0, but am getting cryptography errors when trying to install on Android N (I've tried targeting N, too). Here is the stacktrace:
W/System.err: java.security.InvalidKeyException: Algorithm requires a PBE key
W/System.err: at com.android.org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher.engineInit(BaseBlockCipher.java:564)
W/System.err: at com.android.org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher.engineInit(BaseBlockCipher.java:1006)
W/System.err: at javax.crypto.Cipher.tryTransformWithProvider(Cipher.java:2977)
W/System.err: at javax.crypto.Cipher.tryCombinations(Cipher.java:2884)
W/System.err: at javax.crypto.Cipher$SpiAndProviderUpdater.updateAndGetSpiAndProvider(Cipher.java:2789)
W/System.err: at javax.crypto.Cipher.chooseProvider(Cipher.java:956)
W/System.err: at javax.crypto.Cipher.init(Cipher.java:1199)
W/System.err: at javax.crypto.Cipher.init(Cipher.java:1143)
As you can see, it occurs when calling Cipher.init. Here is my aesDecrypt method:
public static String aesDecrypt(String data, String password) {
try {
String aesKey = getAesKey(password);
byte[] keyValue = Base64.decode(aesKey, Base64.NO_WRAP);
SecretKey key = new SecretKeySpec(keyValue, "AES");
Cipher c = Cipher.getInstance(AES_ALGO);
c.init(Cipher.DECRYPT_MODE, key);
byte[] dataB = Base64.decode(data, Base64.NO_WRAP);
byte[] decVal = c.doFinal(dataB);
return new String(decVal);
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
}
return null;
}
And my getAesKey:
private static String getAesKey(String password) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(password.getBytes("UTF-8"));
return Base64.encodeToString(hash, Base64.NO_WRAP);
} catch (IOException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
I have verified that the key I'm passing in c.init is not null.
Why would this not work on a phone running 7.0?
[EDIT from comments]
The code above uses:
AES_ALGO = "PBEWITHSHA256AND128BITAES-CBC-BC";

The only thing you haven't shown us is the value of AES_ALGO and this is likely to indicate PBE encryption, which the key is not generated using any kind of PBE key derivation (such as PBKDF1 or PBKDF2). Those keys will return a different type of key algorithm than just "AES".
Apparently there is a difference between various versions of Android in this respect. That would not be the first time that there is a usage difference within the Android providers, as they change implementation rather regularly. The API is still identical but the implementation of the algorithms differs somewhat.
To encrypt using CBC mode encryption - as used by the PBE encryption method - please take a look at the countless examples of using "AES/CBC/PKCS5Padding". Don't forget that you need to handle the IV value correctly, something that is included in the Bouncy Castle PBE cipher itself.
[EDIT]
To my surprise the PBE key that is "calculated" by BC simply contains the password and iteration count of the PBE spec. Only util utilized with the cipher is the value calculated. The type of the key is not just SecretKey but:
org.bouncycastle.jcajce.provider.symmetric.util.BCPBEKey
It also contains placeholders for e.g. the IV value. So it's no surprise that the calculation doesn't currently work at all. What's more surprising is that it seems to have worked before.
Moral of the story, don't mix Ciphers and SecretKeys willy-nilly. It may work for specific versions, but the code may crap out when the developers decide to perform more stringent checking.
I tried simply using the name of the PBE algorithm for the key, but obviously that can never work.

Related

How to decrypt Java AES on Nodejs

I have the following code on Java that decrypts AES encryption and I need to do the same on Node.js
private static SecretKeySpec secretKey;
private static byte[] key;
public static void setKey(String myKey) {
MessageDigest sha = null;
try {
key = myKey.getBytes("UTF-8");
sha = MessageDigest.getInstance("SHA-1");
key = sha.digest(key);
key = Arrays.copyOf(key, 16);
secretKey = new SecretKeySpec(key, "AES");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
public static String decrypt(String strToDecrypt, String secret)
{
try
{
setKey(secret);
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5PADDING");
cipher.init(Cipher.DECRYPT_MODE, secretKey);
return new String(cipher.doFinal(Base64.getDecoder().decode(strToDecrypt)));
}
catch (Exception e)
{
System.out.println("Error while decrypting: " + e.toString());
}
return null;
}
I have tried using Crypt under the following code, but it doesn't give me the same results
var aesDecrypt = (text, password, bit) => {
var decipher = crypto.createDecipheriv('aes-' + bit + '-ecb', password, Buffer.alloc(0));
decipher.setAutoPadding(false);
return Buffer.concat([
decipher.update(text, 'base64'),
decipher.final()
]).toString();
};
How could I mimick that Java code from above into Node.js?
As James says, the Java code is hashing (and truncating) the password to form the key. Also it does use standard padding. The following works for ASCII data:
const crypto = require ('crypto');
const mydecrypt = (pw,ctx) => {
var h = crypto.createHash('sha1'); h.update(pw,'utf8'); var k = h.digest().slice(0,16);
var d = crypto.createDecipheriv('aes-128-ecb', k, Buffer.alloc(0));
return Buffer.concat([d.update(ctx,'base64'), d.final()]) .toString();
}
console.log(mydecrypt('password','ks7qtmk7kt5riV/Qyy3glQ=='));
->
testdata
It may not work for non-ASCII data. Java new String(byte[]) uses a JVM-dependent encoding which may be UTF8 or may be something different depending on your platform, build, and environment, none of which you described. OTOH nodejs Buffer.toString() always uses UTF8. You may need to change it to toString(somethingelse) to match the Java.
If this 'password' is truly a password, i.e. chosen or even remembered by one or more human(s), using a simple hash of it is very weak and will probably be broken if used for anything not utterly trivial; you should use a Password-Based Key Derivation Function designed for the purpose by someone competent, like older (PKCS5) PBKDF2 or newer bcrypt, scrypt, or argon2. However, that's not a programming question and is offtopic here; it has been discussed many times and at length on https://crypto.stackexchange.com and https://security.stackexchange.com .

Digitally sign the payload with Private Key using RSASSA-PKCS1-V1_5

I'm trying to interplate and implement the following statement.
Digitally sign the payload with Private Key using
RSASSA-PKCS1-V1_5 signature scheme and SHA1 cryptographic hash function.
Note: Refer to PKCS #1 v2.1: RSA Cryptography Standard specification for PKCS1-v1.5 Signature and Encryption scheme.
I'm confused when it says "and" sha1 hash function, below is adopted code which i'm not sure if it the right interpretation
public String getSignature(String _plainTextMessage,PrivateKey privateKey){
try {
Signature signer = Signature.getInstance("SHA1withRSA");
signer.initSign(privateKey);
signer.update(_plainTextMessage.getBytes());
byte[] signature = signer.sign();
return new BASE64Encoder().encode(signature);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (SignatureException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
or do i need to include MessageDiget like below
public String getSignature(String _plainTextMessage,PrivateKey privateKey){
try {
Signature signer = Signature.getInstance("SHA1withRSA");
signer.initSign(privateKey);
signer.update(_plainTextMessage.getBytes());
byte[] signature = signer.sign();
MessageDigest sha1 = MessageDigest.getInstance("SHA1");
byte[] digest = sha1.digest(signature);
return new BASE64Encoder().encode(digest);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (SignatureException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
I will appreciate any hint, and if applicable how do i verify the signature if i use the second option.
thanks
The first option makes sense and the second option makes very little sense; you'll need the first option: just using SHA1withRSA.
Calculating the hash is part of the signature generation operation. The signature generation operation allows you to configure the signing operation for a specific hash, e.g. SHA-1 or SHA-256. This is what you do when you specify SHA1withRSA. That it is using PKCS#1 v1.5 padding is implicit as at the time they wrote the function there was only one scheme that was widely standardized.
In your second piece of code you hash the signature. That's interesting, but it disallows you to verify the signature using the public key. And that's why you were generating the signature in the first place. Note that if you'd use a different undeterministic signature scheme such as PSS that you would get a different hash each time, rendering your second scheme completely useless.
Note that in general SHA-1 is not considered secure anymore and this is especially the case for signature generation. Only if the input to the signatue algorithm (and the underlying hash algorithm) is restricted could it still be considered secure.

Storing a hmac key in Android keystore

I am using the below code to create a hmac key and returning it as a string.
KeyGenerator keyGen = null;
try {
keyGen = KeyGenerator.getInstance("HmacSHA256");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
SecretKey key = keyGen.generateKey();
byte[] encoded = key.getEncoded();
String s=Base64.encodeToString(encoded, Base64.DEFAULT);
Log.i("Hmac key before encrypt",s);
try {
KeyStore keystore = KeyStore.getInstance("AndroidKeyStore");
keystore.load(null, null);
KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) keystore.getEntry("temp", null);
RSAPublicKey publicKey = (RSAPublicKey) privateKeyEntry.getCertificate().getPublicKey();
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] cipherBytes = cipher.doFinal(encoded);
return Base64.encodeToString(cipherBytes,Base64.DEFAULT);
} catch (UnrecoverableEntryException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyStoreException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (CertificateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
How can I store this in the android keystore?. I have tried using the below code:
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
KeyStore.ProtectionParameter param = new KeyStore.PasswordProtection("test".toCharArray());
keyStore.setEntry("key1",hmacKey,param);
I get an errors no matter what format hmacKey is in: String/Bytes or javax.crypto.SecretKey. Below are the errors:
In case of passing Key hmacKey:
Wrong 2nd argument type. Found: 'java.security.Key', required: 'java.security.KeyStore.Entry'
Same in cases where I pass a string or byte array.
If I typecast the parameter to java.security.KeyStore.Entry, it still doesn't work.
Is this the correct way of doing so? Can anyone give pointers as to how the HMAC key can be stored in the keystore using an alias. How can convert the hmack key to java.security.KeyStore.Entry format?
The Android key store was created to allow you to use asymmetric keys and symmetric keys outside your application code. As specified in the training material:
Key material never enters the application process. When an application performs cryptographic operations using an Android Keystore key, behind the scenes plaintext, ciphertext, and messages to be signed or verified are fed to a system process which carries out the cryptographic operations. If the app's process is compromised, the attacker may be able to use the app's keys but will not be able to extract their key material (for example, to be used outside of the Android device).
So the idea of generating the key inside the application code - and thus outside the key store - is not a good idea. How to generate a secret key inside the key store is defined for HMAC keys in the API for the KeyGenParameterSpec class:
KeyGenerator keyGenerator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_HMAC_SHA256, "AndroidKeyStore");
keyGenerator.initialize(
new KeyGenParameterSpec.Builder("key2", KeyProperties.PURPOSE_SIGN).build());
SecretKey key = keyGenerator.generateKey();
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(key);
...
// The key can also be obtained from the Android Keystore any time as follows:
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
key = (SecretKey) keyStore.getKey("key2", null);
Other key types can be found in the KeyProperties class

Common Encryption algorithm for Rails and Java

I am going to integrate two web applications written in different platforms (Java and Ruby),
I have to use common encryption algorithm for password in both application.
Is there any common encryption/decryption algorithm for both? If yes, please mention any useful link or any example.
It would be highly appreciated. Thanks in advance
In addition during my digging out it I found,
I have used Base64 with DES in both, interesting thing is that Characters and special characters give me same result in both but as i adding any number like (1,2,3), half of result is same and half encryption is something different.
*Ruby Code
require 'openssl'
require 'base64'
c = OpenSSL::Cipher::Cipher.new("des")
c.encrypt
c.key ="REPPIFY_ABCDEFGHIJKLMNOPQRSTUVWXYZ"
e = c.update("ankit#123")
e << c.final
puts Base64.encode64(e)
Output: Cbe9GslMs8mh33jAOD9qsw==
*Java Code
I am defining only encryption method here:-
public static String encryptPassword(String pass) {
public static final String DESKEY = "REPPIFY_ABCDEFGHIJKLMNOPQRSTUVWXYZ";
System.out.println("Here is my password = "+pass);
DESKeySpec keySpec = null;
SecretKeyFactory keyFactory = null;
SecretKey key = null;
Cipher cipher = null;
BASE64Encoder base64encoder = new BASE64Encoder();
byte[] cleartext = null;
String encrypedPwd = null;
String pass = "ankit#123";
try {
keySpec = new DESKeySpec(DESKEY.getBytes("UTF8"));
keyFactory = SecretKeyFactory.getInstance("DES");
key = keyFactory.generateSecret(keySpec);
if(pass!=null) {
cleartext = pass.getBytes("UTF8");
cipher = Cipher.getInstance("DES");
cipher.init(Cipher.ENCRYPT_MODE, key);
encrypedPwd = base64encoder.encode(cipher.doFinal(cleartext));
}
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeySpecException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} // cipher is not thread safe
catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
}
System.out.println("Here I am printing encrypted pwd = "+encrypedPwd);
return encrypedPwd;
}
Output in Java :- Cbe9GslMs8mWn9yTmZrUiw==
Well, in the Ruby world, I'd recommend BCrypt, which is also favored by popular authentication plugins like Devise. I'm not very familiar with Java but a quick search suggests that there's BCrypt implementation in Java too:
http://www.mindrot.org/projects/jBCrypt/
EDIT - BCrypt is 1-way encryption, mainly for use in hashing passwords. If you are looking for something that will encrypt and decrypt then you'll have to look at something else. Seeing as you mentioned it's for passwords I'd suggest you only want 1-way encryption though.
I got the answer...just change a following line in ruby code and then you can use base64 decoder with DES in both:
c = OpenSSL::Cipher::Cipher.new("DES-ECB")
require 'openssl'
require 'base64'
c = OpenSSL::Cipher::Cipher.new("DES-ECB")
c.encrypt
c.key ="REPPIFY_ABCDEFGHIJKLMNOPQRSTUVWXYZ"
e = c.update("ankit#123")
e << c.final
puts Base64.encode64(e)

AES-128 Encryption not working on Java < 1.7

I've been chipping away at a school assignment for 3 days, and finally finished it today, error-free and working fine! Except, I was testing it on Java 1.7, and the school servers (where the professor will compile it) run 1.6. So, I tested my code on 1.6, wanting to cover all my bases, and I get a BadPaddingException upon decryption.
[EDIT] Warning: this code does not follow common security practices and should not be used in production code.
Originally, I had this, which works fine on 1.7 (sorry, lots of code.. all relevant..):
public static String aes128(String key, String data, final int direction) {
SecureRandom rand = new SecureRandom(key.getBytes());
byte[] randBytes = new byte[16];
rand.nextBytes(randBytes);
SecretKey encKey = new SecretKeySpec(randBytes, "AES");
Cipher cipher = null;
try {
cipher = Cipher.getInstance("AES");
cipher.init((direction == ENCRYPT ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE), encKey);
} catch (InvalidKeyException e) {
return null;
} catch (NoSuchPaddingException e) {
return null;
} catch (NoSuchAlgorithmException e) {
return null;
}
try {
if (direction == ENCRYPT) {
byte[] encVal = cipher.doFinal(data.getBytes());
String encryptedValue = Base64.encode(encVal);
return encryptedValue;
} else {
byte[] dataBytes = Base64.decode(data);
byte[] encVal = cipher.doFinal(dataBytes);
return new String(encVal);
}
} catch (NullPointerException e) {
return null;
} catch (BadPaddingException e) {
return null;
} catch (IllegalBlockSizeException e) {
return null;
}
}
However, my BadPaddingException catch block executes upon decryption:
javax.crypto.BadPaddingException: Given final block not properly padded
at com.sun.crypto.provider.SunJCE_f.b(DashoA13*..)
at com.sun.crypto.provider.SunJCE_f.b(DashoA13*..)
at com.sun.crypto.provider.AESCipher.engineDoFinal(DashoA13*..)
at javax.crypto.Cipher.doFinal(DashoA13*..)
at CipherUtils.aes128(CipherUtils.java:112)
at CipherUtils.decryptFile(CipherUtils.java:44)
at decryptFile.main(decryptFile.java:21)
This is what I tried to fix it (basically, I added all the padding/unpadding myself, and used NoPadding):
public static String aes128(String key, String data, final int direction) {
// PADCHAR = (char)0x10 as String
while (key.length() % 16 > 0)
key = key + PADCHAR; // Added this loop
SecureRandom rand = new SecureRandom(key.getBytes());
byte[] randBytes = new byte[16];
rand.nextBytes(randBytes);
SecretKey encKey = new SecretKeySpec(randBytes, "AES");
AlgorithmParameterSpec paramSpec = new IvParameterSpec(key.getBytes()); // Created this
Cipher cipher = null;
try {
cipher = Cipher.getInstance("AES/CBC/NoPadding"); // Added CBC/NoPadding
cipher.init((direction == ENCRYPT ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE), encKey, paramSpec); // Added paramSpec
} catch (InvalidKeyException e) {
return null;
} catch (NoSuchPaddingException e) {
return null;
} catch (NoSuchAlgorithmException e) {
return null;
} catch (InvalidAlgorithmParameterException e) {
return null; // Added this catch{}
}
try {
if (direction == ENCRYPT) {
while (data.length() % 16 > 0)
data = data + PADCHAR; // Added this loop
byte[] encVal = cipher.doFinal(data.getBytes());
String encryptedValue = Base64.encode(encVal);
return encryptedValue;
} else {
byte[] dataBytes = Base64.decode(data);
byte[] encVal = cipher.doFinal(dataBytes);
return new String(encVal);
}
} catch (NullPointerException e) {
return null;
} catch (BadPaddingException e) {
return null;
} catch (IllegalBlockSizeException e) {
return null;
}
}
When using this, I just get gibberish in and out:
Out: u¢;èÉ÷JRLòB±J°N°[9cRÐ{ªv=]I¯¿©:
´RLA©êí;R([¶Ü9¸ßv&%®µ^#û|Bá (80)
Unpadded: u¢;èÉ÷JRLòB±J°N°[9cRÐ{ªv=]I¯¿©:
´RLA©êí;R([¶Ü9¸ßv&%®µ^#û|Bá (79)
It is also worth noting that 1.6 and 1.7 produce different encrypted strings.
For example, on 1.7, encrypting xy (including a SHA-1 hash) with key hi produces:
XLUVZBIJv1n/FV2MzaBK3FLPQRCQF2FY+ghyajdqCGsggAN4aac8bfwscrLaQT7BMHJgfnjJLn+/rwGv0UEW+dbRIMQkNAwkGeSjda3aEpk=
On 1.6, the same thing produces:
nqeahRnA0IuRn7HXUD1JnkhWB5uq/Ng+srUBYE3ycGHDC1QB6Xo7cPU6aEJxH7NKqe3kRN3rT/Ctl/OrhqVkyDDThbkY8LLP39ocC3oP/JE=
I didn't expect the assignment to take so long, so my time has run out and it does need to be done tonight. If there is no answer by then, however, I'll just leave a note to my teacher regarding this. It appears to be some issue that was fixed in 1.7... though hopefully can be remedied through the correct addition/fix in my code.
Thanks a ton for everyone's time!
First off:
For almost all systems, encrypting the same plaintext twice should always (i.e. with very very high probability) produce different ciphertext.
The traditional example is that it allows a CPA adversary to distinguish E("attack at dawn") from E("attack at dusk") with only two queries. (There are a handful of systems where you want deterministic encryption, but the right way to do this is "synthetic IV" or cipher modes like CMC and EME.)
Ultimately, the problem is that SecureRandom() is not intended for key derivation.
If the input "key" is a passphrase, you should be using something like PBKDF2 (or scrypt() or bcrypt()).
Additionally, you should be using an explicit charset, e.g. String.getBytes("UTF-8").
If the input "key" is a key, the most common string representation is a hexdump. Java doesn't include an unhexing function, but there are several here.
If the input is a "master key" and you want to derive a subkey, then you should be hashing it with other data. There's not much point if the subkey is always the same.
Additional nitpicks:
Your code is vulnerable to a padding oracle attack; you really should be verifying a MAC before doing anything with the data (or better, using an authenticated encryption mode).
In your second listing, you explicitly reuse the IV. Bad! Assuming CBC mode, the IV used should be unpredictable; SecureRandom is useful here.
I've been looking over and over and I have to agree with NullUserException. The problem is the use of SecureRandom. This means that you never really know what your key is and therefore it is not necessarily ever the same key.
encKey comes from SecureRandom, which is seeded by the key provided. Therefore, if the key is the same, the seed is the same, so the random should be the same...
...unless of course Oracle (or another provider) changes the implementation between versions.
Okay, adding more information that I researched. I think this answer was most helpful.
Get password and cleartext from the user, and convert them to byte arrays.
Generate a secure random salt.
Append the salt to the password and compute its cryptographic hash. Repeat this many times.
Encrypt the cleartext using the resulting hash as the initialization vector and/or secret key.
Save the salt and the resulting ciphertext.
To me, it sounds like SecureRandom is used once to generate a salt but then salt must be saved with the cypher text in order to undo the cyphering process. Additional security comes from repetition and variance of steps (obscurity).
Note: I couldn't find any consensus that these steps are best practices.

Categories