Storing a hmac key in Android keystore - java

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

Related

How can I encrypt a large message with a 512 RSA key?

I want to encrypt a large message and perform a measurement with different key sizes.
public static void generate(int keylen)
{
KeyPairGenerator keygen = null;
try
{
keygen = KeyPairGenerator.getInstance("RSA");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
keygen.initialize(keylen);
key = keygen.generateKeyPair();
}
When I select 512 as the key size, my program throws the following error message: javax.crypto.IllegalBlockSizeException: Data must not be longer than 53 bytes
public static byte[] encrypt(String message, PublicKey pk)
{
Cipher cipher = null;
try
{
cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, pk);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
}
byte[] cipherText = null;
try {
ciphertext = cipher.doFinal(message.getBytes(StandardCharsets.UTF_8));
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
}
return cipherText;
}
I know that the RSA algorithm can only encrypt data that has a maximum byte length of the RSA key length in bits divided with eight minus eleven padding bytes, i.e. number of maximum bytes = key length in bits / 8 - 11.
I also know that 512 is not secure. I want to measure different key sizes with their respective time e.g. 512, 1024, 2048, 4096.....
Is there a way to encrypt the message without using a hybrid method because I also need to measure the time.
I read that there is a method to divide the message into blocks. I am not sure how this works exactly.

Find similar to X509EncodedKeySpec in C#

All I need is just to get this lines of code in C#, Actually I can't find X509EncodedKeySpec in C#:
byte[] keyByte = Base64.decode(publicKey);
// generate public key
X509EncodedKeySpec s = new X509EncodedKeySpec(keyByte);
KeyFactory factory = null;
try
{
factory = KeyFactory.getInstance("RSA");
}
catch (NoSuchAlgorithmException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
Key pubKey = null;
try
{
pubKey = factory.generatePublic(s);
}
catch (InvalidKeySpecException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
As of .NET Core 3.0 ImportSubjectPublicKeyInfo is available
https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.rsa.importsubjectpublickeyinfo?view=netcore-3.0
You should be able to use it as follows:
using (RSA rsa = RSA.Create())
{
rsa.ImportSubjectPublicKeyInfo(publicKey, out _);
//do your stuff
}
X509EncodedKeySpec is Java's version of X.509 SubjectPublicKeyInfo. .NET doesn't have any API built-in for reading SubjectPublicKeyInfo. For best results, you want to have a full certificate, then use cert.GetRSAPublicKey().
If you can get the transmitted as the RSA Modulus and RSA (Public) Exponent values then you could build the RSAParameters object to load into a key.
using (RSA rsa = RSA.Create())
{
rsa.ImportParameters(new RSAParameters { Modulus = modulus, Exponent = exponent });
// do public key things
}

Android N InvalidKeyException

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.

Decrypt data using RSA between PHP and Java Android issue

I am using a PHP server to encrypt some data and then decrypt it to an Android device.
But when I try to decrypt it on the Android device side, I get the following error :
javax.crypto.BadPaddingException: error:0407106B:rsa
routines:RSA_padding_check_PKCS1_type_2:block type is not 02
When I am on
Cipher.getInstance("RSA/ECB/PKCS1Padding");
I am encrypting the value on a PHP server using PHPSeclips Library (last github version at this date) and signing as well. This part does actually works because already used to be decoded on a Javacard program so the error does not actually belongs here.
This is the way I proceed on Android side :
protected byte[] decryptData(String alias, byte[] data) throws InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException, NoSuchProviderException, BadPaddingException, IllegalBlockSizeException, UnsupportedOperationException {
Log.i(TAG, "decryptData() Decrypt data " + HexStringConverter.byteArrayToHexString(data));
byte[] decryptedData = null;
PrivateKey privateKey = getPrivateKey(alias);
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
decryptedData = cipher.doFinal(data);
Log.i(TAG, "decryptData() Decrypted data: " + HexStringConverter.byteArrayToHexString(decryptedData));
return decryptedData;
}
With the getPrivateKey() method :
protected RSAPrivateKey getPrivateKey(String alias) {
try {
KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
ks.load(null);
KeyStore.Entry entry = ks.getEntry(alias, null);
if (!(entry instanceof KeyStore.PrivateKeyEntry)) {
Log.w(TAG, "getPrivateKey() Not an instance of a PrivateKeyEntry");
}
return (RSAPrivateKey) ((KeyStore.PrivateKeyEntry) entry).getPrivateKey();
} catch (NoSuchAlgorithmException e) {
Log.w(TAG, "getPrivateKey() ", e);
} catch (KeyStoreException e) {
Log.w(TAG, "getPrivateKey() ", e);
} catch (CertificateException e) {
Log.w(TAG, "getPrivateKey() ", e);
} catch (IOException e) {
Log.w(TAG, "getPrivateKey() ", e);
} catch (UnrecoverableEntryException e) {
Log.w(TAG, "getPrivateKey() ", e);
}
return null;
}
And how it's encrypted on PHP side :
//Function for encrypting with RSA
function rsa_encrypt($string, $key)
{
require_once(__DIR__ . '/../phpseclib/Crypt/RSA.php');
//Create an instance of the RSA cypher and load the key into it
$cipher = new Crypt_RSA();
$cipher->loadKey($key);
//Set the encryption mode
$cipher->setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1);
//Return the encrypted version
return $cipher->encrypt($string);
}
//Function for decrypting with RSA
function rsa_sign($string, $key)
{
require_once(__DIR__ . '/../phpseclib/Crypt/RSA.php');
//Create an instance of the RSA cypher and load the key into it
$cipher = new Crypt_RSA();
$cipher->loadKey($key);
//Set the signature mode
$cipher->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1);
//Return the signed message
return $cipher->sign($string);
}
UPDATE (7/3/14) For those who get the same mistakes I recommand you look at the following page : http://hustoknow.blogspot.ca/2013/01/rsa-block-type-is-not-02-error.html
In fact this put me on the right way to discover what the issue was.
The error raised tells that the decryption key does not match the key used to encrypt it, so I've watched every Public Key used for this exchange. Because I used 1024 key size I was parsing the input message (modulus + public exponent) with relevant size of each. And I noticed that the modulus was not fully received compared to the one displayed on the Android device. So here is the mistake, Android by default use 2048 key size when you use KeyPairGenerator object for key generation. Just set manually the key size to 1024 fix this issue.
Example:
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
Calendar notBefore = Calendar.getInstance();
Calendar notAfter = Calendar.getInstance();
notAfter.add(Calendar.YEAR, 1);
KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(mContext)
.setAlias(alias)
.setKeySize(1024)
.setSubject(
new X500Principal(String.format("CN=%s, OU=%s", alias,
mContext.getPackageName())))
.setSerialNumber(BigInteger.ONE).setStartDate(notBefore.getTime())
.setEndDate(notAfter.getTime()).build();
keyPairGenerator.initialize(spec);
KeyPair kp = keyPairGenerator.generateKeyPair();
Hope this help.
You might need to do define('CRYPT_RSA_PKCS15_COMPAT', true).
The following comment block elaborates:
https://github.com/phpseclib/phpseclib/blob/a8c2ff0fb013169193c649adab512cafef5068cf/phpseclib/Crypt/RSA.php#L2272
Basically, OpenSSL (and quite potentially Java too) implements PKCS#1 v1.5 whereas phpseclib implements PKCS#1 v2.1. PKCS#1 v2.1 modifies PKCS1 style encryption to make use of randomized padding so no two ciphertext's will ever be the same.

BadPaddingException: Given final block not properly padded

I have a private key file encripted with DES/ECB/PKCS5Padding (56 bit DES key generated by a secret phrase) and I want to decrypt it.
I don't know why, but everytime I try to decript, the method doFinal of my cipher class is throwing this error:
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.DESCipher.engineDoFinal(DashoA13*..) at
javax.crypto.Cipher.doFinal(DashoA13*..) at...
Here is my code:
public static PrivateKey readPrivateKeyFromFile(File file, String chaveSecreta) {
try {
SecureRandom r = new SecureRandom(chaveSecreta.getBytes());
KeyGenerator keyGen = KeyGenerator.getInstance("DES");
keyGen.init(56, r);
Key key = keyGen.generateKey();
byte[] privateKeyBytes = decryptPKFile(file, key);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
PrivateKey privateKey = null;
try {
privateKey = keyFactory.generatePrivate(privateKeySpec);
} catch (InvalidKeySpecException e) {
JOptionPane.showMessageDialog(null, "Erro 01, tente mais tarde");
}
return privateKey;
} catch (NoSuchAlgorithmException e) {
JOptionPane.showMessageDialog(null, "Erro 02, tente mais tarde");
}
return null;
}
public static byte[] decryptPKFile(File file, Key key){
try{
Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
byte[] cipherText = readBytes(file);
cipher.init(Cipher.DECRYPT_MODE, key);
System.out.println(cipher);
System.out.println(cipherText);
byte[] text = cipher.doFinal(cipherText);
return text;
}catch(Exception e){
e.printStackTrace();
return null;
}
}
public static byte[] readBytes(File file) {
try {
FileInputStream fs = new FileInputStream(file);
byte content[] = new byte[(int) file.length()];
fs.read(content);
return content;
} catch (FileNotFoundException e) {
System.out.println("Arquivo não encontrado!");
e.printStackTrace();
} catch (IOException ioe) {
System.out.println("Erro ao ler arquivo!");
ioe.printStackTrace();
}
return null;
}
Any syggestions?
You're trying to decrypt ciphertext with a random number generator created using a specific seed. However, you don't specify the algorithm, and the algorithm may change internally as well. Android is even known to generate a fully random value instead for some versions.
You need to use a SecretKeyFactory not a KeyGenerator. And you will of course need the 8-byte key data. The only way to retrieve this in your case is to find the SecureRandom algorithm/implementation before and re-calculate the key.
Now any ciphertext will decrypt with any key. DES ECB only provides (some sort of) confidentiality, not integrity. The problem is that it will decrypt into garbage. Now if you try to remove the padding from garbage you will likely get a padding error.
If you're "lucky" - once in about 256 times - you will get a result. This happens when the decrypted block ends with 01 or 0202, that's valid padding. The result will - of course - be garbage as well, but it will not end with a BadPaddingException. In your case the SecureRandom instance is likely to return the same incorrect value over and over though, so this may never happen.
In the future, please use PBKDF2 and feed it the encoded password. Clearly note the character encoding used, Java SE uses the lowest 8 bits of the char array. Never ever use String.getBytes() as the default encoding may differ between systems.

Categories