I want to store my AES-256 key to AndroidKeyStore, this AES-256 key is raw key (a random 32 byte). I try some code like this.
public foo () {
SecureRandom sr = new SecureRandom();
byte[] key = new byte[32];
sr.nextBytes(key);
try {
KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
ks.load(null);
SecretKeySpec sks = new SecretKeySpec(key, "AES");
SecretKeyFactory skf = SecretKeyFactory.getInstance("AES");
SecretKey sk = skf.generateSecret(sks);
ks.setEntry("key", new KeyStore.SecretKeyEntry(sk), new
KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT).build());
KeyStore.SecretKeyEntry entry = (KeyStore.SecretKeyEntry) ks.getEntry("key", null);
SecretKey skLoad = (SecretKey) ks.getKey("key", null);
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, skLoad);
Log.i(TAG, Arrays.toString(cipher.doFinal(plainBytes)));
} catch (Exception e) {
e.printStackTrace();
}
}
I get the exception at line SecretKey sk = skf.generateSecret(sks);
java.security.spec.InvalidKeySpecException: To generate secret key in Android Keystore, use KeyGenerator initialized with android.security.keystore.KeyGenParameterSpec
I know we can save key with using KeyGenerator with KeyGenParameterSpec, but I have some reason to use owner key and KeyGenParameter seem can't import my owner key. So have any idea for this problem, thank all!
Generally you should not import keys from outside the key store, as they are insecure before they enter. So adding them later has limited benefits.
However, you can do a little trick: create a wrapping key in the key store and use it to wrap your symmetric key, and store the result. Then you can simply reverse the process when the key is needed again.
Unfortunately the best methods for storing keys such as (GCM-)SIV mode is generally not implemented, but hey, now you've at least heard about it.
Related
I want to mail encrypted log file from my app. Since logs can be larger I have encrypted data using AES and encrypted the key using RSA. Since the AES key is required to decrypt the log, I am sending the encrypted key and logs in the same file.
Question 1: Is this right approach ? If not what is the best approach to follow in this scenario.Below is the code for the same.
public static String encrypt(String data) {
StringBuilder encryptedData = new StringBuilder();
try {
// Generate AES key.
KeyGenerator generator = KeyGenerator.getInstance("AES");
// The AES key size in number of bits.
generator.init(256);
SecretKey secKey = generator.generateKey();
// Initialize AES Cipher, IV and encrypt string.
Cipher aesCipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
aesCipher.init(Cipher.ENCRYPT_MODE, secKey, new IvParameterSpec(new byte[16]));
byte[] byteCipherText = aesCipher.doFinal(data.getBytes());
String encryptedText = Base64.encodeToString(byteCipherText, Base64.DEFAULT);
// Initialize RSA Cipher and generate public key.
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
X509EncodedKeySpec publicSpec = new X509EncodedKeySpec(Base64.decode(PUBLIC_KEY, Base64.DEFAULT));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey puKey = keyFactory.generatePublic(publicSpec);
cipher.init(Cipher.PUBLIC_KEY, puKey);
// Encrypt key and text.
byte[] encryptedKey = cipher.doFinal(secKey.getEncoded());
String aesKey = Base64.encodeToString(encryptedKey, Base64.DEFAULT);
encryptedData.append(aesKey);
encryptedData.append(encryptedText);
} catch (Exception e) {
e.printStackTrace();
}
return encryptedData.toString();
}
Since the AES key is required to decrypt the log, I am sending the encrypted key and logs in the same file.
Question 1: Is this right approach ? If not what is the best approach to follow in this scenario.Below is the code for the same.
The approach is correct, what I'm missing is authentication (HMAC, GCM, ...).
There are some standards how to bundle the encrypted key, content and authentication together (e.g. CMS, PKCS7, OpenPGP, ..) however if it's for your own application, you may do it your way (don't bother with standards).
If you want to use RSA use RSA-KEM
Well, using RSA KEM you may save a little of performance skipping the padding, but I'd try if it is feasible for you. As well there's an issue when encrypting the same key material with different public keys.
I'd keep it simple - just use the properly padded RSA encryption.
I'd suggest to use OAEP padding RSA/ECB/OAEPWithSHA-256AndMGF1Padding instead of PKCS1Padding (OAEP is considered newer/safer)
I need to implement 256 bit AES encryption, I refer this- http://nelenkov.blogspot.jp/2015/06/keystore-redesign-in-android-m.html
The KeyGenerator looks like this .
KeyGenerator keyGenerator = KeyGenerator
.getInstance(KeyProperties.KEY_ALGORITHM_AES,"AndroidKeyStore");
KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(keyName,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setUserAuthenticationValidityDurationSeconds(5 *11160)
.build();
keyGenerator.init(spec);
SecretKey key1 = keyGenerator.generateKey();
When I import encoded value of the key from keystore, it returns null to me.
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
SecretKey key3 = (SecretKey) keyStore.getKey(keyName, null);
Log.d("Test MM",key3.getEncoded()+",");
I found null value of key3.getEncoded() in logcat.Please give me some suggestions.
Symmetric keys generated in the keystore are unexportable in Android M. So it works exactly as it is supposed to. You can still use the key with a Cipher to encrypt/decrypt, but you cannot get the raw key bytes. Generally you should need to either.
For an Android app I want to obfuscate/encrypt the server public key when building with gradle.
Right now I'm obfuscating using Base64 but I need AES as an extra
task encryptKeys {
doFirst {
//Encrypt the server key
// Load key
byte[] key = new File('project/keys/server.crt.der').bytes
// Encode key twice
String encoded = key.encodeBase64().toString();
encoded = encoded.bytes.encodeBase64().toString();
//TODO AES ENCRYPTION HERE
// Save key
new File('project/src/main/assets/server.crt.der').bytes = encoded.getBytes()
Later at runtime when using this key i would decrypt it like this
public static String decrypt(byte[] cipherText) throws Exception{
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding", "BC");
SecretKeySpec key = new SecretKeySpec(encryptionKey.getBytes("UTF-8"), "AES");
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(IV.getBytes("UTF-8")));
return new String(cipher.doFinal(cipherText),"UTF-8");
}
What would be the correct way to encrypt my key with AES in gradle script? Google couldn't help me out. Is this something that's possible at all or would I need to find another solution?
There's a similar SO question here for encrypting a string with AES in java.
I've adopted this into a gradle script below.
It will encrypt the SERVERKEY string (in your version load this from external source) with the key KEY. I don't have BouncyCastle installed, so I used SunJCE, but I left it as a parameter so you can change it easily.
The output in this simple case is the file "obf.enc". The decIt task will also decrypt and print out to show it's worked symmetrically.
Your hardest part is obviously the fact your KEY for encrypting is embedded in your application (hence my question in the comments), so this is just security through obscurity, but if that's good enough for the application, so be it.
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
import javax.crypto.Cipher
ext {
KEY = "mysecretkey".padRight(16).getBytes("UTF-8")
SERVERKEY = "serverkey"
IV = "1234".padRight(16).getBytes("UTF-8")
PROVIDER = "SunJCE"
}
task encIt << {
SecretKeySpec key = new SecretKeySpec(KEY, "AES")
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding", PROVIDER)
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(IV))
def encBytes = cipher.doFinal(SERVERKEY.bytes)
def out = file('obf.enc')
out.delete()
out << encBytes
}
task decIt << {
def cipherText = file('obf.enc').bytes
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding", PROVIDER)
SecretKeySpec key = new SecretKeySpec(KEY, "AES")
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(IV))
println new String(cipher.doFinal(cipherText), "UTF-8")
}
I had tried to run the following AES/ CBC/ PKCS5Padding encryption and decryption code, with SHA-1 as key generation, in Nexus 5. It works very well so far.
However, my only concern is, Is AES/ CBC/ PKCS5Padding encryption decryption algorithm and SHA-1 hashing algorithm available in all type of Android devices?
Is there any chance that the following code will fail to run on certain Android devices? If so, is there any fall back plan?
AES/ CBC/ PKCS5Padding
// http://stackoverflow.com/questions/3451670/java-aes-and-using-my-own-key
public static byte[] generateKey(String key) throws GeneralSecurityException, UnsupportedEncodingException {
byte[] binary = key.getBytes("UTF-8");
MessageDigest sha = MessageDigest.getInstance("SHA-1");
binary = sha.digest(binary);
// Use only first 128 bit.
binary = Arrays.copyOf(binary, 16);
return binary;
}
// http://stackoverflow.com/questions/17322002/what-causes-the-error-java-security-invalidkeyexception-parameters-missing
public static String encrypt(byte[] key, String value) throws GeneralSecurityException {
// Argument validation.
if (key.length != 16) {
throw new IllegalArgumentException("Invalid key size.");
}
// Setup AES tool.
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(new byte[16]));
// Do the job with AES tool.
byte[] original = value.getBytes(Charset.forName("UTF-8"));
byte[] binary = cipher.doFinal(original);
return Base64.encodeToString(binary, Base64.DEFAULT);
}
// // http://stackoverflow.com/questions/17322002/what-causes-the-error-java-security-invalidkeyexception-parameters-missing
public static String decrypt(byte[] key, String encrypted) throws GeneralSecurityException {
// Argument validation.
if (key.length != 16) {
throw new IllegalArgumentException("Invalid key size.");
}
// Setup AES tool.
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, skeySpec, new IvParameterSpec(new byte[16]));
// Do the job with AES tool.
byte[] binary = Base64.decode(encrypted, Base64.DEFAULT);
byte[] original = cipher.doFinal(binary);
return new String(original, Charset.forName("UTF-8"));
}
Usage
byte[] key = generateKey("my secret key");
String ciphertext = encrypt(key, "my plain content");
String plainContent = decrypt(key, ciphertext);
No, it's unlikely to the extreme that it will fail. The Android API has been derived from the Java API's. The Java API's have contained the "AES/CBC/PKCS5Padding" since version 1.4.
As for "SHA-1", that's an even older algorithm, which has been supported since time began.
Beware not to use "PKCS7Padding" instead. Java uses "PKCS5Padding" as replacement, "PKCS7Padding" support may be sketchy even if it means the same thing.
Note that you should be using password based encryption (PBE) instead of AES/CBC and SHA-1. Especially using SHA-1 as key derivation method is particularly dangerous as you don't use a salt or work factor as a good Password Based Key Derivation Function such as PBKDF2 should. Basically only do this if you know your password contains enough entropy.
Using an all zero IV for the same key is worse though (as already indicated in the comments). It lets attackers find repeats of (the starting blocks of) plaintext input. Authenticated encryption (e.g. using HMAC-SHA-1) is always recommended and more or less required for transport mode encryption (as opposed to in-place encryption where plaintext/padding oracle attacks are not possible).
This isn't answering your question directly, but...
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(new byte[16]));
Do not use this construct! It will break any security you think you're getting!
This invocation initialises your cipher object with an all-zeros initialisation vector. This is a very very very bad thing, especially with CBC: CBC is quite malleable, and doesn't do any integrity-protection. Make sure you generate your IV using SecureRandom or similar, and preferably use GCM or CCM.
String text = name1.getText().toString();
// Sending side
byte[] data = null;
try {
data = text.getBytes("UTF-8");
} catch (UnsupportedEncodingException e1) {
e1.printStackTrace();
}
String base64 = Base64.encodeToString(data, Base64.DEFAULT);
was able to encrypt password and will to decrypt the same password but i have something in mind that im not sure of this is my first time trying to encrypt a password. Is it safe to encrypt the password this way because I tried encrypt a password : zxc and the result is just a four letter password (its result is : enhj) so im wondering if it is a safe way to encrypt the password. Any ideas on how to remake the code to make it safer and not easy to decode and ideas on how to decrypt the encrypted password?
UPDATE: This is a sample of encryption and decryption I found at this site here but I cant make it run.
encryption
String password = "password";
int iterationCount = 1000;
int keyLength = 256;
int saltLength = keyLength / 8; // same size as key output
SecureRandom random = new SecureRandom();
byte[] salt = new byte[saltLength];
randomb.nextBytes(salt);
KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt,
iterationCount, keyLength);
SecretKeyFactory keyFactory = SecretKeyFactory
.getInstance("PBKDF2WithHmacSHA1");
byte[] keyBytes = keyFactory.generateSecret(keySpec).getEncoded();
SecretKey key = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
byte[] iv = new byte[cipher.getBlockSize());
random.nextBytes(iv);
IvParameterSpec ivParams = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, key, ivParams);
byte[] ciphertext = cipher.doFinal(plaintext.getBytes("UTF-8"));
decryption
String[] fields = ciphertext.split("]");
byte[] salt = fromBase64(fields[0]);
byte[] iv = fromBase64(fields[1]);
byte[] cipherBytes = fromBase64(fields[2]);
// as above
SecretKey key = deriveKeyPbkdf2(salt, password);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivParams = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, key, ivParams);
byte[] plaintext = cipher.doFinal(cipherBytes);
String plainrStr = new String(plaintext , "UTF-8");
You've tagged this cryptography, passwords, and encryption, so I'll answer it as such.
First, Base64 is not actually encryption, it's merely encoding - essentially changing from 8 bit bytes to 6 bit bytes, and your test is perfect - 3*8 bit characters = 24 bits. 24bits/6bits = 4 Base64 characters. I've also verified that enhj is indeed the Base64 encoding of zxc on my own C implementation of Base64. For further evidence of this, note that you didn't provide any encryption key!
Second, for user authentication (which is what I assume you're doing), do not encrypt passwords - that's a major blunder Adobe just made. For user authentication, you don't ever need to see the user's password again - you merely need to verify that they entered the same thing they did before. Thus, when they enter a password the first time, you salt and hash it. The next time, you retrieve the salt you used the first time, and hash the freshly entered password with the same salt (and # of iterations/work factor) - if the result is the same as you have on record, let them in, since giving the same password will get the same result.
The three canonical answers to How to securely hash passwords? are PBKDF2, Bcrypt, and Scrypt. A quick Google search regarding Android password hashing turned up:
How can I make sure password hashing is secure on computers while not being prohibitively slow on mobile devices? and safe to use jBCrypt and recommend it to my organization? which refer to the mindrot jBCrypt Java library and/or the Spring Security variant of jBCrypt
PBKDF2 with SHA256 on android refers to a SpongyCastle 1.47+ implementation of PBKDF2-HMAC-SHA-256 as well references to PBKDF2-HMAC-SHA-1.
PKCS5S2ParametersGenerator generator = new PKCS5S2ParametersGenerator(new SHA256Digest());
generator.init(PBEParametersGenerator.PKCS5PasswordToUTF8Bytes(password), salt, iterations);
KeyParameter key = (KeyParameter)generator.generateDerivedMacParameters(keySizeInBits);
The Android-developers blogspot article Using Cryptography to Store Credentials Safely also references PBKDF2-HMAC-SHA-1.
public static SecretKey generateKey(char[] passphraseOrPin, byte[] salt) throws NoSuchAlgorithmException, InvalidKeySpecException {
// Number of PBKDF2 hardening rounds to use. Larger values increase
// computation time. You should select a value that causes computation
// to take >100ms.
final int iterations = 8000;
// Generate a 160-bit key
final int outputKeyLength = 160;
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec keySpec = new PBEKeySpec(passphraseOrPin, salt, iterations, outputKeyLength);
SecretKey secretKey = secretKeyFactory.generateSecret(keySpec);
return secretKey;
}
In ALL cases, choose as high an iteration count/work factor as you can stand the delay of (using as fast a library for your chosen algorithm as you can abide by the license of). Your salt should be a cryptographically random series of bytes in the 8 to 16 byte length range.
For PBKDF2 in particular, never use more outputBytes than the native hash size or you give an attacker a comparative advantage - SHA-1's native size is 20 bytes, SHA-256 is 32 bytes, and SHA-512 is 64 bytes natively.
If you really do need encryption rather than authentication, the "Using Cryptography to Store Credentials Safely" link above covers that too, though the better answer is to store the salt and number of iterations/work factor and simply regenerate the key from the password each time - if the data decrypts, it was good. If not, well, bad password.
You are not encrypting anything. You are converting bytes to base64 encoding. You need to use a ciphering algorithm. See http://examples.javacodegeeks.com/core-java/security/simple-symmetric-key-encrypt-decrypt/