I get a 64 uncompressed public key and need to run ECDH to generate a shared secret.
In order to call ECDH I need to convert the byte array to PublicKey and I am using the following code I have found in this forum:
public static void setOtherPublicKey(byte[] publicKeyBytes) throws NoSuchAlgorithmException, InvalidKeySpecException
{
try {
//EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(encodedPrivateKey);
KeyFactory generator = KeyFactory.getInstance("EC");
//PrivateKey privateKey = generator.generatePrivate(privateKeySpec);
EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeyBytes);
blePubKey = generator.generatePublic(publicKeySpec);
} catch (Exception e) {
throw new IllegalArgumentException("Failed to create KeyPair from provided encoded keys", e);
}
}
This code throws an InvalidKeySpecException.
As example, the public key of the other party is:
9b5e9a5a971877530c9cadbbea93c2ee2483d65052678f745bad79f110173520
54019832e11376537a76c4defd0b3dfdc667a974239147f323cdcfd2baa39892
Adding the code after getting the answers below:
public static void setOtherPublicKey() throws NoSuchAlgorithmException, InvalidKeySpecException
{
// first generate key pair of your own
ECPublicKey pubKey = (ECPublicKey) SecPage.g_kpA.getPublic();
ECParameterSpec params = pubKey.getParams();
int keySizeBytes = params.getOrder().bitLength() / Byte.SIZE;
// get the other party 64 bytes
//byte [] otherPub = crypto.getBlePubKeyBytes();
byte[] otherPub = hexStringToByteArray("ac2bdd28fce5c7b181b34f098b0934742281246ed907a5f646940c1edcb724e7c7358356aebea810322a8e324cc77f376df4cabd754110ad41ec178c0a6b8e5f");
ByteArrayBuffer xBytes = new ByteArrayBuffer(33);
ByteArrayBuffer yBytes = new ByteArrayBuffer(33);
byte[] zero = {(byte)0x00};
xBytes.append(zero, 0, 1);
xBytes.append(otherPub, 0, 32);
yBytes.append(zero, 0, 1);
yBytes.append(otherPub, 32, 32);
// generate the public key point
BigInteger x = new BigInteger(xBytes.buffer());
BigInteger y = new BigInteger(yBytes.buffer());
ECPoint w = new ECPoint(x, y);
// generate the key of the other side
ECPublicKeySpec otherKeySpec = new ECPublicKeySpec(w , params);
KeyFactory keyFactory = KeyFactory.getInstance("EC");
blePubKey = (ECPublicKey) keyFactory.generatePublic(otherKeySpec);
}
Well, whaty'know, you can actually do this... explanation in the comments.
public class ECDHPub {
private static ECPublicKey decodeECPublicKey(ECParameterSpec params,
final byte[] pubkey) throws NoSuchAlgorithmException,
InvalidKeySpecException {
int keySizeBytes = params.getOrder().bitLength() / Byte.SIZE;
int offset = 0;
BigInteger x = new BigInteger(1, Arrays.copyOfRange(pubkey, offset,
offset + keySizeBytes));
offset += keySizeBytes;
BigInteger y = new BigInteger(1, Arrays.copyOfRange(pubkey, offset,
offset + keySizeBytes));
ECPoint w = new ECPoint(x, y);
ECPublicKeySpec otherKeySpec = new ECPublicKeySpec(w, params);
KeyFactory keyFactory = KeyFactory.getInstance("EC");
ECPublicKey otherKey = (ECPublicKey) keyFactory
.generatePublic(otherKeySpec);
return otherKey;
}
private static byte[] encodeECPublicKey(ECPublicKey pubKey) {
int keyLengthBytes = pubKey.getParams().getOrder().bitLength()
/ Byte.SIZE;
byte[] publicKeyEncoded = new byte[2 * keyLengthBytes];
int offset = 0;
BigInteger x = pubKey.getW().getAffineX();
byte[] xba = x.toByteArray();
if (xba.length > keyLengthBytes + 1 || xba.length == keyLengthBytes + 1
&& xba[0] != 0) {
throw new IllegalStateException(
"X coordinate of EC public key has wrong size");
}
if (xba.length == keyLengthBytes + 1) {
System.arraycopy(xba, 1, publicKeyEncoded, offset, keyLengthBytes);
} else {
System.arraycopy(xba, 0, publicKeyEncoded, offset + keyLengthBytes
- xba.length, xba.length);
}
offset += keyLengthBytes;
BigInteger y = pubKey.getW().getAffineY();
byte[] yba = y.toByteArray();
if (yba.length > keyLengthBytes + 1 || yba.length == keyLengthBytes + 1
&& yba[0] != 0) {
throw new IllegalStateException(
"Y coordinate of EC public key has wrong size");
}
if (yba.length == keyLengthBytes + 1) {
System.arraycopy(yba, 1, publicKeyEncoded, offset, keyLengthBytes);
} else {
System.arraycopy(yba, 0, publicKeyEncoded, offset + keyLengthBytes
- yba.length, yba.length);
}
return publicKeyEncoded;
}
public static void main(String[] args) throws Exception {
// (only) required for named curves other than those used in JCE
Security.addProvider(new BouncyCastleProvider());
// create local and remote key
KeyPairGenerator kpgen = KeyPairGenerator.getInstance("ECDH", "BC");
ECGenParameterSpec genspec = new ECGenParameterSpec("brainpoolp256r1");
kpgen.initialize(genspec);
KeyPair localKeyPair = kpgen.generateKeyPair();
KeyPair remoteKeyPair = kpgen.generateKeyPair();
// test generation
byte[] encodedRemotePublicKey = encodeECPublicKey((ECPublicKey) remoteKeyPair
.getPublic());
// test creation
ECPublicKey remoteKey = decodeECPublicKey(
((ECPublicKey) localKeyPair.getPublic()).getParams(),
encodedRemotePublicKey);
// local key agreement
KeyAgreement localKA = KeyAgreement.getInstance("ECDH");
localKA.init(localKeyPair.getPrivate());
localKA.doPhase(remoteKey, true);
byte[] localSecret = localKA.generateSecret();
// remote key agreement
KeyAgreement remoteKA = KeyAgreement.getInstance("ECDH");
remoteKA.init(remoteKeyPair.getPrivate());
remoteKA.doPhase((ECPublicKey) localKeyPair.getPublic(), true);
byte[] remoteSecret = localKA.generateSecret();
// validation
System.out.println(Arrays.equals(localSecret, remoteSecret));
}
}
Related
I am trying to have secret (String) in the app, anywhere, save to be! So I came up with this idea to use the keyStore to store the key and only use it for encryption and decryption of my secret. Here is how I save (encrypt) my secret:
public static boolean setKeyStoreString(String strToStore, Context context) {
if (strToStore == null) return false;
if (strToStore.length() == 0) return false;
Log.e(TAG, strToStore);
try {
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
int nBefore = keyStore.size();
// Create the keys if necessary
if (!keyStore.containsAlias("phrase")) {
KeyGenerator generator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder("phrase", KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setKeySize(256)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setUserAuthenticationValidityDurationSeconds(-1)
.setRandomizedEncryptionRequired(false)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
.setUserAuthenticationRequired(false)
.build();
generator.init(spec);
generator.generateKey();
}
int nAfter = keyStore.size();
Log.v(TAG, "Before = " + nBefore + " After = " + nAfter);
String filesDirectory = context.getFilesDir().getAbsolutePath();
String encryptedDataFilePath = filesDirectory + File.separator + "my_phrase";
// Log.v(TAG, "strPhrase = " + strToStore);
// Log.v(TAG, "dataDirectory = " + dataDirectory);
// Log.v(TAG, "filesDirectory = " + filesDirectory);
// Log.v(TAG, "encryptedDataFilePath = " + encryptedDataFilePath);
SecretKey secret = (SecretKey) keyStore.getKey("phrase", null);
Cipher inCipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
inCipher.init(Cipher.ENCRYPT_MODE, secret);
CipherOutputStream cipherOutputStream = new CipherOutputStream(
new FileOutputStream(encryptedDataFilePath), inCipher);
byte[] bytesToStore = strToStore.getBytes("UTF-8");
cipherOutputStream.write(bytesToStore);
try {
cipherOutputStream.close();
} catch (Exception ex) {
ex.printStackTrace();
}
return true;
} catch (Exception e) {
Log.e(TAG, Log.getStackTraceString(e));
}
return false;
}
and here is how I try to retrieve it:
public static String getKeyStoreString(final Context context) {
KeyStore keyStore;
String recoveredSecret = "";
String filesDirectory = context.getFilesDir().getAbsolutePath();
String encryptedDataFilePath = filesDirectory + File.separator + "my_phrase";
try {
keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
SecretKey secretKey = (SecretKey)
keyStore.getKey("phrase", null);
if (secretKey == null) throw new RuntimeException("secretKey is null");
Cipher outCipher;
outCipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
outCipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(
new byte[outCipher.getBlockSize()]));
CipherInputStream cipherInputStream = new CipherInputStream(
new FileInputStream(encryptedDataFilePath), outCipher);
byte[] roundTrippedBytes = new byte[1000]; //TODO: dynamically resize as we get more data
int index = 0;
int nextByte;
while ((nextByte = cipherInputStream.read()) != -1) {
roundTrippedBytes[index] = (byte) nextByte;
index++;
}
recoveredSecret = new String(roundTrippedBytes, 0, index, "UTF-8");
Log.e(TAG, "round tripped string = " + recoveredSecret);
} catch (Exception e) {
e.printStackTrace();
}
Log.e(TAG, "recovered: " + recoveredSecret);
return recoveredSecret;
}
the problem is - the result comes a little damaged in the beginning,
i.e: some cool text for extraction >>>>> �k��X�&�ALqM,A� text for
extraction
i have the same problem i used Base64.encode(your byte[], Base64.DEFAULT);to encode and Base64.decode(your byte[], Base64.DEFAULT) to decode
try to put byte[] encode = Base64.encode(bytesToStore, Base64.DEFAULT)
after byte[] bytesToStore = strToStore.getBytes("UTF-8");
in the public static boolean setKeyStoreString() and check in the "encode" log
I am trying to implement client side encryption/decryption on Android platform by referencing IOS implementations. I am wrestling with the question that encryption and decryption on Android and IOS platform are different even they have used the same algorithm. Let`s say, when Android device encrypt and upload a file to server, the IOS device couldn`t download and decrypt it correctly.
The algorithm I am using
Encrypt the file key with the user provided password. We first use PBKDF2 algorithm (1000 iteratioins of SHA256) to derive a key/iv pair from the password, then use AES 256/CBC to encrypt the file key. The result is called the "encrypted file key". This encrypted file key will be sent to and stored on the server. When you need to access the data, you can decrypt the file key from the encrypted file key.
All file data is encrypted by the file key with AES 256/CBC. We use PBKDF2 algorithm (1000 iterations of SHA256) to derive key/iv pair from the file key.
store the derived key/iv pair locally and use them to encrypt files. After encryption, the data is uploaded to the server. The same with decrypt files when downloading files.
Android code
private static final String TAG = Crypto.class.getSimpleName();
private static final String CIPHER_ALGORITHM = "AES/CBC/NoPadding";
private static int KEY_LENGTH = 32;
private static int KEY_LENGTH_SHORT = 16;
// minimum values recommended by PKCS#5, increase as necessary
private static int ITERATION_COUNT = 1000;
// Should generate random salt for each repo
private static byte[] salt = {(byte) 0xda, (byte) 0x90, (byte) 0x45, (byte) 0xc3, (byte) 0x06, (byte) 0xc7, (byte) 0xcc, (byte) 0x26};
private Crypto() {
}
/**
* decrypt repo encKey
*
* #param password
* #param randomKey
* #param version
* #return
* #throws UnsupportedEncodingException
* #throws NoSuchAlgorithmException
*/
public static String deriveKeyPbkdf2(String password, String randomKey, int version) throws UnsupportedEncodingException, NoSuchAlgorithmException {
if (TextUtils.isEmpty(password) || TextUtils.isEmpty(randomKey)) {
return null;
}
PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator(new SHA256Digest());
gen.init(PBEParametersGenerator.PKCS5PasswordToUTF8Bytes(password.toCharArray()), salt, ITERATION_COUNT);
byte[] keyBytes;
if (version == 2) {
keyBytes = ((KeyParameter) gen.generateDerivedMacParameters(KEY_LENGTH * 8)).getKey();
} else
keyBytes = ((KeyParameter) gen.generateDerivedMacParameters(KEY_LENGTH_SHORT * 8)).getKey();
SecretKey realKey = new SecretKeySpec(keyBytes, "AES");
final byte[] iv = deriveIVPbkdf2(realKey.getEncoded());
return seafileDecrypt(fromHex(randomKey), realKey, iv);
}
public static byte[] deriveIVPbkdf2(byte[] key) throws UnsupportedEncodingException {
PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator(new SHA256Digest());
gen.init(key, salt, 10);
return ((KeyParameter) gen.generateDerivedMacParameters(KEY_LENGTH_SHORT * 8)).getKey();
}
/**
* All file data is encrypted by the file key with AES 256/CBC.
*
* We use PBKDF2 algorithm (1000 iterations of SHA256) to derive key/iv pair from the file key.
* After encryption, the data is uploaded to the server.
*
* #param plaintext
* #param key
* #return
*/
private static byte[] seafileEncrypt(byte[] plaintext, SecretKey key, byte[] iv) {
try {
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
IvParameterSpec ivParams = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, key, ivParams);
return cipher.doFinal(plaintext);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
Log.e(TAG, "NoSuchAlgorithmException " + e.getMessage());
return null;
} catch (InvalidKeyException e) {
e.printStackTrace();
Log.e(TAG, "InvalidKeyException " + e.getMessage());
return null;
} catch (InvalidAlgorithmParameterException e) {
e.printStackTrace();
Log.e(TAG, "InvalidAlgorithmParameterException " + e.getMessage());
return null;
} catch (NoSuchPaddingException e) {
e.printStackTrace();
Log.e(TAG, "NoSuchPaddingException " + e.getMessage());
return null;
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
Log.e(TAG, "IllegalBlockSizeException " + e.getMessage());
return null;
} catch (BadPaddingException e) {
e.printStackTrace();
Log.e(TAG, "BadPaddingException " + e.getMessage());
return null;
}
}
/**
* All file data is encrypted by the file key with AES 256/CBC.
*
* We use PBKDF2 algorithm (1000 iterations of SHA256) to derive key/iv pair from the file key.
* After encryption, the data is uploaded to the server.
*
* #param plaintext
* #param key
* #return
*/
public static byte[] encrypt(byte[] plaintext, String key, byte[] iv, int version) throws NoSuchAlgorithmException {
PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator(new SHA256Digest());
gen.init(PBEParametersGenerator.PKCS5PasswordToUTF8Bytes(key.toCharArray()), salt, ITERATION_COUNT);
byte[] keyBytes;
if (version == 2) {
keyBytes = ((KeyParameter) gen.generateDerivedMacParameters(KEY_LENGTH * 8)).getKey();
} else
keyBytes = ((KeyParameter) gen.generateDerivedMacParameters(KEY_LENGTH_SHORT * 8)).getKey();
SecretKey realKey = new SecretKeySpec(keyBytes, "AES");
return seafileEncrypt(plaintext, realKey , iv);
}
IOS code
+ (int)deriveKey:(const char *)data_in inlen:(int)in_len version:(int)version key:(unsigned char *)key iv:(unsigned char *)iv
{
unsigned char salt[8] = { 0xda, 0x90, 0x45, 0xc3, 0x06, 0xc7, 0xcc, 0x26 };
if (version == 2) {
PKCS5_PBKDF2_HMAC (data_in, in_len,
salt, sizeof(salt),
1000,
EVP_sha256(),
32, key);
PKCS5_PBKDF2_HMAC ((char *)key, 32,
salt, sizeof(salt),
10,
EVP_sha256(),
16, iv);
return 0;
} else if (version == 1)
return EVP_BytesToKey (EVP_aes_128_cbc(), /* cipher mode */
EVP_sha1(), /* message digest */
salt, /* salt */
(unsigned char*)data_in,
in_len,
1 << 19, /* iteration times */
key, /* the derived key */
iv); /* IV, initial vector */
else
return EVP_BytesToKey (EVP_aes_128_ecb(), /* cipher mode */
EVP_sha1(), /* message digest */
NULL, /* salt */
(unsigned char*)data_in,
in_len,
3, /* iteration times */
key, /* the derived key */
iv); /* IV, initial vector */
}
+(int)seafileEncrypt:(char **)data_out outlen:(int *)out_len datain:(const char *)data_in inlen:(const int)in_len version:(int)version key:(uint8_t *)key iv:(uint8_t *)iv
{
int ret, blks;
EVP_CIPHER_CTX ctx;
EVP_CIPHER_CTX_init (&ctx);
if (version == 2)
ret = EVP_EncryptInit_ex (&ctx,
EVP_aes_256_cbc(), /* cipher mode */
NULL, /* engine, NULL for default */
key, /* derived key */
iv); /* initial vector */
else if (version == 1)
ret = EVP_EncryptInit_ex (&ctx,
EVP_aes_128_cbc(), /* cipher mode */
NULL, /* engine, NULL for default */
key, /* derived key */
iv); /* initial vector */
else
ret = EVP_EncryptInit_ex (&ctx,
EVP_aes_128_ecb(), /* cipher mode */
NULL, /* engine, NULL for default */
key, /* derived key */
iv); /* initial vector */
if (ret == DEC_FAILURE)
return -1;
blks = (in_len / BLK_SIZE) + 1;
*data_out = (char *)malloc (blks * BLK_SIZE);
if (*data_out == NULL) {
Debug ("failed to allocate the output buffer.\n");
goto enc_error;
}
int update_len, final_len;
/* Do the encryption. */
ret = EVP_EncryptUpdate (&ctx,
(unsigned char*)*data_out,
&update_len,
(unsigned char*)data_in,
in_len);
if (ret == ENC_FAILURE)
goto enc_error;
/* Finish the possible partial block. */
ret = EVP_EncryptFinal_ex (&ctx,
(unsigned char*)*data_out + update_len,
&final_len);
*out_len = update_len + final_len;
/* out_len should be equal to the allocated buffer size. */
if (ret == ENC_FAILURE || *out_len != (blks * BLK_SIZE))
goto enc_error;
EVP_CIPHER_CTX_cleanup (&ctx);
return 0;
enc_error:
EVP_CIPHER_CTX_cleanup (&ctx);
*out_len = -1;
if (*data_out != NULL)
free (*data_out);
*data_out = NULL;
return -1;
}
+ (void)generateKey:(NSString *)password version:(int)version encKey:(NSString *)encKey key:(uint8_t *)key iv:(uint8_t *)iv
{
unsigned char key0[32], iv0[16];
char passwordPtr[256] = {0}; // room for terminator (unused)
[password getCString:passwordPtr maxLength:sizeof(passwordPtr) encoding:NSUTF8StringEncoding];
if (version < 2) {
[NSData deriveKey:passwordPtr inlen:(int)password.length version:version key:key iv:iv];
return;
}
[NSData deriveKey:passwordPtr inlen:(int)password.length version:version key:key0 iv:iv0];
char enc_random_key[48], dec_random_key[48];
int outlen;
hex_to_rawdata(encKey.UTF8String, enc_random_key, 48);
[NSData seafileDecrypt:dec_random_key outlen:&outlen datain:(char *)enc_random_key inlen:48 version:2 key:key0 iv:iv0];
[NSData deriveKey:dec_random_key inlen:32 version:2 key:key iv:iv];
}
- (NSData *)encrypt:(NSString *)password encKey:(NSString *)encKey version:(int)version
{
uint8_t key[kCCKeySizeAES256+1] = {0}, iv[kCCKeySizeAES128+1];
[NSData generateKey:password version:version encKey:encKey key:key iv:iv];
char *data_out;
int outlen;
int ret = [NSData seafileEncrypt:&data_out outlen:&outlen datain:self.bytes inlen:(int)self.length version:version key:key iv:iv];
if (ret < 0) return nil;
return [NSData dataWithBytesNoCopy:data_out length:outlen];
}
Here is the complete project for anyone who found this useful.
Support client side encryption #487
How does an encrypted library work?
Hi me too had the same problem.
I found 2 things causing mismatch for my code:
1. ios and android encryption algorithms was not same(i have requested PKCS7Padding algorithm in ios and i tried NoPadding and AES/CBC/PKCS5Padding algorithms in android.
2. The key generated in ios is different than in Android.
Please see my worked code both gives same output in ios and android:
IOS:
- (NSString *) encryptString:(NSString*)plaintext withKey:(NSString*)key {
NSData *data = [[plaintext dataUsingEncoding:NSUTF8StringEncoding] AES256EncryptWithKey:key];
return [data base64EncodedStringWithOptions:kNilOptions];
}
- (NSString *) decryptString:(NSString *)ciphertext withKey:(NSString*)key {
if ([ciphertext isKindOfClass:[NSString class]]) {
NSData *data = [[NSData alloc] initWithBase64EncodedString:ciphertext options:kNilOptions];
return [[NSString alloc] initWithData:[data AES256DecryptWithKey:key] encoding:NSUTF8StringEncoding];
}
return nil;
}
Android:(We modified aes w.r.t iOS default method)
private static String Key = "your key";
public static String encryptString(String stringToEncode) throws NullPointerException {
try {
SecretKeySpec skeySpec = getKey(Key);
byte[] clearText = stringToEncode.getBytes("UTF8");
final byte[] iv = new byte[16];
Arrays.fill(iv, (byte) 0x00);
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, ivParameterSpec);
String encrypedValue = Base64.encodeToString(cipher.doFinal(clearText), Base64.DEFAULT);
return encrypedValue;
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
public static String encryptString(String stringToEncode) throws NullPointerException {
try {
SecretKeySpec skeySpec = getKey(Key);
byte[] clearText = stringToEncode.getBytes("UTF8");
final byte[] iv = new byte[16];
Arrays.fill(iv, (byte) 0x00);
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
cipher.init(Cipher.DECRYPT_MODE, skeySpec, ivParameterSpec);
byte[] cipherData = cipher.doFinal(Base64.decode(stringToEncode.getBytes("UTF-8"), Base64.DEFAULT));
String decoded = new String(cipherData, "UTF-8");
return decoded;
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
private static SecretKeySpec getKey(String password) throws UnsupportedEncodingException {
int keyLength = 256;
byte[] keyBytes = new byte[keyLength / 8];
Arrays.fill(keyBytes, (byte) 0x0);
byte[] passwordBytes = password.getBytes("UTF-8");
int length = passwordBytes.length < keyBytes.length ? passwordBytes.length : keyBytes.length;
System.arraycopy(passwordBytes, 0, keyBytes, 0, length);
SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
return key;
}
Hope this help u guys
Thanks
For certain reasons I need to implement Rijndael de/compression with a blocksize of 256 bits instead of AES which uses a block size of 128 bits (reason: data is encrypted in PHP using Rijndael...).
How can I change the block-size for a cipher?
If i just get a cipher with "RIJNDAEL/CFB/PKCS5Padding" and try to initialize a IV with 256 bits I get an exception, because the block-size is only 128 bits.
There is no support in any of the Sun JCE providers for anything other than Rijndael with the 128-bit blocksize: this is the AES algorithm. To get rijndael with the 256-bit blocksize you will have to go somewhere else. I suggest the Bouncycastle java library. The RijndaelEngine class has a constructor that accepts a block size in bits. Most people find the PaddedBufferedBlockCipher class to be more convenient when used with suitable padding, e.g.
PaddedBufferedBlockCipher c = new PaddedBufferedBlockCipher(new RijndaelEngine(256), new PKCS7Padding());
Note that PHP mcrypt uses Zero Byte padding so new ZeroBytePadding() should be used instead of new PKCS7Padding().
Bellow a full implementation using CBC and RIJNDAEL 256.
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.engines.RijndaelEngine;
import org.bouncycastle.crypto.modes.CBCBlockCipher;
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.bouncycastle.crypto.paddings.ZeroBytePadding;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
import org.bouncycastle.util.encoders.Base64;
public static String encryptWithAesCBC(String plaintext, String key, String iv)
{
try {
PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new RijndaelEngine(256)), new ZeroBytePadding());
CipherParameters ivAndKey = new ParametersWithIV(new KeyParameter(key.getBytes()), iv.getBytes());
cipher.init(true, ivAndKey);
return new String(Base64.encode(cipherData(cipher, plaintext.getBytes())));
} catch (InvalidCipherTextException e) {
throw new RuntimeException(e);
}
}
public static String decryptWithAesCBC(String encrypted, String key, String iv)
{
try {
byte[] ciphertext = Base64.decode(encrypted);
PaddedBufferedBlockCipher aes = new PaddedBufferedBlockCipher(new CBCBlockCipher(new RijndaelEngine(256)), new ZeroBytePadding());
CipherParameters ivAndKey = new ParametersWithIV(new KeyParameter(key.getBytes()), iv.getBytes());
aes.init(false, ivAndKey);
return new String(cipherData(aes, ciphertext));
} catch (InvalidCipherTextException e) {
throw new RuntimeException(e);
}
}
private static byte[] cipherData(PaddedBufferedBlockCipher cipher, byte[] data) throws InvalidCipherTextException
{
int minSize = cipher.getOutputSize(data.length);
byte[] outBuf = new byte[minSize];
int length1 = cipher.processBytes(data, 0, data.length, outBuf, 0);
int length2 = cipher.doFinal(outBuf, length1);
int actualLength = length1 + length2;
byte[] cipherArray = new byte[actualLength];
for (int x = 0; x < actualLength; x++) {
cipherArray[x] = outBuf[x];
}
return cipherArray;
}
private String md5(String string)
{
try {
java.security.MessageDigest md = java.security.MessageDigest.getInstance("MD5");
byte[] array = md.digest(string.getBytes());
StringBuffer sb = new StringBuffer();
for (int i = 0; i < array.length; ++i) {
sb.append(Integer.toHexString((array[i] & 0xFF) | 0x100).substring(1, 3));
}
return sb.toString();
} catch (java.security.NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
When using CFB, PaddedBufferedBlockCipher should be replace by the following:
PaddedBufferedBlockCipher aes = new PaddedBufferedBlockCipher(new CFBBlockCipher(new RijndaelEngine(256),8), new ZeroBytePadding());
// PHP mcrypt uses a blocksize of 8 bit for CFB
Usage:
String salt = "fbhweui3497";
String key = md5(salt);
String iv = md5(md5(salt));
String encrypted = encryptWithAesCBC("text to encript", key, iv);
String decrypted = decryptWithAesCBC(encrypted, key, iv);
First of all, I've already seen
Android 4.2 broke my AES encrypt/decrypt code
and
Encryption error on Android 4.2
and the provided solution:
SecureRandom sr = null;
if (android.os.Build.VERSION.SDK_INT >= JELLY_BEAN_4_2) {
sr = SecureRandom.getInstance("SHA1PRNG", "Crypto");
} else {
sr = SecureRandom.getInstance("SHA1PRNG");
}
doesn't work for me, because, when decoding data encrypted in Android<4.2 in Android 4.2, I get:
javax.crypto.BadPaddingException: pad block corrupted
at com.android.org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher.engineDoFinal(BaseBlockCipher.java:709)
My code is quite simple, and was working until Android 4.2:
public static byte[] encrypt(byte[] data, String seed) throws Exception {
KeyGenerator keygen = KeyGenerator.getInstance("AES");
SecureRandom secrand = SecureRandom.getInstance("SHA1PRNG");
secrand.setSeed(seed.getBytes());
keygen.init(128, secrand);
SecretKey seckey = keygen.generateKey();
byte[] rawKey = seckey.getEncoded();
SecretKeySpec skeySpec = new SecretKeySpec(rawKey, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
return cipher.doFinal(data);
}
public static byte[] decrypt(byte[] data, String seed) throws Exception {
KeyGenerator keygen = KeyGenerator.getInstance("AES");
SecureRandom secrand = SecureRandom.getInstance("SHA1PRNG");
secrand.setSeed(seed.getBytes());
keygen.init(128, secrand);
SecretKey seckey = keygen.generateKey();
byte[] rawKey = seckey.getEncoded();
SecretKeySpec skeySpec = new SecretKeySpec(rawKey, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
return cipher.doFinal(data);
}
My guess is that the default provider wasn't the only thing that changed in Android 4.2, otherwise my code would work with the proposed solution.
My code was based on some post I found here at StackOverflow a long time ago; I see that it differs from the mentioned posts as it just crypts and decrypts byte arrays, whereas the others solutions crypt and decrypt Strings (HEX Strings, I think).
Does it have to do with the seed? Does it have a min/max length, restriction of chars, etc?
Any idea / solution?
EDIT:
After a lot of tests, I see that there are 2 problems:
The provider changed in Android 4.2 (API 17) -> This one is easy to fix, just apply the solution I mentioned at top of the post
BouncyCastle changed from 1.34 to 1.45 in Android 2.2 (API 8)->Android2.3 (API 9), so the decryption problem I previously told is the same as described here: BouncyCastle AES error when upgrading to 1.45
So now the question is: is there any way to recover data crypted in BouncyCastle 1.34 in BouncyCastle 1.45+?
First a disclaimer:
DO NOT ever use SecureRandom to derive a key! This is broken and doesn't make sense!
The following block of code from the question tries to deterministically derive a key from a password, called the "seed" as the password is used to "seed" the random number generator.
KeyGenerator keygen = KeyGenerator.getInstance("AES");
SecureRandom secrand = SecureRandom.getInstance("SHA1PRNG");
secrand.setSeed(seed.getBytes());
keygen.init(128, secrand);
SecretKey seckey = keygen.generateKey();
However, the "SHA1PRNG" algorithm is not well defined and implementations of "SHA1PRNG" may return different or even fully random keys as a result.
If you're reading an AES key from disk, just store the actual key and don't go through this weird dance. You can get a SecretKey for AES usage from the bytes by doing:
SecretKey key = new SecretKeySpec(keyBytes, "AES");
If you're using a password to derive a key, follow Nelenkov's excellent tutorial with the caveat that a good rule of thumb is the salt size should be the same size as the key output.
The iterationCount (work factor) is of course subject to change and should be changed as CPU power progresses - generally it is recommended not to go lower than 40 to 100K as of 2018. Beware that PBKDF2 only adds a constant time delay to guessing passwords; it is not a replacement for really weak passwords.
It looks like this:
/* User types in their password: */
String password = "password";
/* Store these things on disk used to derive key later: */
int iterationCount = 1000;
int saltLength = 32; // bytes; should be the same size as the output (256 / 8 = 32)
int keyLength = 256; // 256-bits for AES-256, 128-bits for AES-128, etc
byte[] salt; // Should be of saltLength
/* When first creating the key, obtain a salt with this: */
SecureRandom random = new SecureRandom();
byte[] salt = new byte[saltLength];
random.nextBytes(salt);
/* Use this to derive the key from the password: */
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");
That's it. Anything else you should not use.
private static final int ITERATION_COUNT = 1000;
private static final int KEY_LENGTH = 256;
private static final String PBKDF2_DERIVATION_ALGORITHM = "PBKDF2WithHmacSHA1";
private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding";
private static final int PKCS5_SALT_LENGTH = 32;
private static final String DELIMITER = "]";
private static final SecureRandom random = new SecureRandom();
public static String encrypt(String plaintext, String password) {
byte[] salt = generateSalt();
SecretKey key = deriveKey(password, salt);
try {
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
byte[] iv = generateIv(cipher.getBlockSize());
IvParameterSpec ivParams = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, key, ivParams);
byte[] cipherText = cipher.doFinal(plaintext.getBytes("UTF-8"));
if(salt != null) {
return String.format("%s%s%s%s%s",
toBase64(salt),
DELIMITER,
toBase64(iv),
DELIMITER,
toBase64(cipherText));
}
return String.format("%s%s%s",
toBase64(iv),
DELIMITER,
toBase64(cipherText));
} catch (GeneralSecurityException e) {
throw new RuntimeException(e);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
public static String decrypt(String ciphertext, String password) {
String[] fields = ciphertext.split(DELIMITER);
if(fields.length != 3) {
throw new IllegalArgumentException("Invalid encypted text format");
}
byte[] salt = fromBase64(fields[0]);
byte[] iv = fromBase64(fields[1]);
byte[] cipherBytes = fromBase64(fields[2]);
SecretKey key = deriveKey(password, salt);
try {
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
IvParameterSpec ivParams = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, key, ivParams);
byte[] plaintext = cipher.doFinal(cipherBytes);
return new String(plaintext, "UTF-8");
} catch (GeneralSecurityException e) {
throw new RuntimeException(e);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
private static byte[] generateSalt() {
byte[] b = new byte[PKCS5_SALT_LENGTH];
random.nextBytes(b);
return b;
}
private static byte[] generateIv(int length) {
byte[] b = new byte[length];
random.nextBytes(b);
return b;
}
private static SecretKey deriveKey(String password, byte[] salt) {
try {
KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, ITERATION_COUNT, KEY_LENGTH);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(PBKDF2_DERIVATION_ALGORITHM);
byte[] keyBytes = keyFactory.generateSecret(keySpec).getEncoded();
return new SecretKeySpec(keyBytes, "AES");
} catch (GeneralSecurityException e) {
throw new RuntimeException(e);
}
}
private static String toBase64(byte[] bytes) {
return Base64.encodeToString(bytes, Base64.NO_WRAP);
}
private static byte[] fromBase64(String base64) {
return Base64.decode(base64, Base64.NO_WRAP);
}
Source
The problem is that with the new provider, the following snippet of code
KeyGenerator keygen = KeyGenerator.getInstance("AES");
SecureRandom secrand = SecureRandom.getInstance("SHA1PRNG");
secrand.setSeed(seed.getBytes());
keygen.init(128, secrand);
SecretKey seckey = keygen.generateKey();
byte[] rawKey = seckey.getEncoded();
generates a different, genuinely random rawKey every time it's executed. So, you're trying to decrypt with a key different from the one used to encrypt data and you get the exception. You won't be able to recover your key or data when it has been generated this way, and only the seed has been saved.
What fixed it for me (as #Giorgio suggested) was just replacing this:
SecureRandom secrand = SecureRandom.getInstance("SHA1PRNG");
with this:
SecureRandom secrand = SecureRandom.getInstance("SHA1PRNG", "Crypto");
I am unable to give you answer to your asked question but I'd simply try to work this around >- if you face some problems with bouncycastle across devices/OS version, you should ditch built-in versions completely and instead add bouncycastle as jar to your project, change your import to point to that jar, rebuild and assuming it all works you'd be immune to android built-in version changes from now on.
Because all of this didn't help me to generate an encrypted password which was deterministic on all android devices (>=2.1), I searched for another AES implementation. I found one which works for me on all devices. I'm not a security specialist, I'm not sure if the technique isn't as secure as it could be. I'm only posting the code for people who have run in the same problem that I had face before.
import java.security.GeneralSecurityException;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import android.util.Log;
public class EncodeDecodeAES {
private static final String TAG_DEBUG = "TAG";
private IvParameterSpec ivspec;
private SecretKeySpec keyspec;
private Cipher cipher;
private String iv = "fedcba9876543210";//Dummy iv (CHANGE IT!)
private String SecretKey = "0123456789abcdef";//Dummy secretKey (CHANGE IT!)
public EncodeDecodeAES() {
ivspec = new IvParameterSpec(iv.getBytes());
keyspec = new SecretKeySpec(SecretKey.getBytes(), "AES");
try {
cipher = Cipher.getInstance("AES/CBC/NoPadding");
} catch (GeneralSecurityException e) {
Log.d(TAG_DEBUG, e.getMessage());
}
}
public byte[] encrypt(String text) throws Exception {
if (text == null || text.length() == 0)
throw new Exception("Empty string");
byte[] encrypted = null;
try {
cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec);
encrypted = cipher.doFinal(padString(text).getBytes());
} catch (Exception e) {
Log.d(TAG_DEBUG, e.getMessage());
throw new Exception("[encrypt] " + e.getMessage());
}
return encrypted;
}
public byte[] decrypt(String code) throws Exception {
if (code == null || code.length() == 0)
throw new Exception("Empty string");
byte[] decrypted = null;
try {
cipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec);
decrypted = cipher.doFinal(hexToBytes(code));
} catch (Exception e) {
Log.d(TAG_DEBUG, e.getMessage());
throw new Exception("[decrypt] " + e.getMessage());
}
return decrypted;
}
public static String bytesToHex(byte[] data) {
if (data == null) {
return null;
}
int len = data.length;
String str = "";
for (int i = 0; i < len; i++) {
if ((data[i] & 0xFF) < 16)
str = str + "0" + java.lang.Integer.toHexString(data[i] & 0xFF);
else
str = str + java.lang.Integer.toHexString(data[i] & 0xFF);
}
return str;
}
public static byte[] hexToBytes(String str) {
if (str == null) {
return null;
} else if (str.length() < 2) {
return null;
} else {
int len = str.length() / 2;
byte[] buffer = new byte[len];
for (int i = 0; i < len; i++) {
buffer[i] = (byte) Integer.parseInt(str.substring(i * 2, i * 2 + 2), 16);
}
return buffer;
}
}
private static String padString(String source) {
char paddingChar = ' ';
int size = 16;
int x = source.length() % size;
int padLength = size - x;
for (int i = 0; i < padLength; i++) {
source += paddingChar;
}
return source;
}
}
You can use it like:
EncodeDecodeAES aes = new EncodeDecodeAES ();
/* Encrypt */
String encrypted = EncodeDecodeAES.bytesToHex(aes.encrypt("Text to Encrypt"));
/* Decrypt */
String decrypted = new String(aes.decrypt(encrypted));
Source: HERE
It's does have to do with the seed indeed and it's also should use multiple of 8 (like 8, 16, 24 or 32), try complete the seed with A's and B's or 1's and 0s (has to be something like this ABAB..., because AAA.. or BBB.. will not work also.) up to reach a multiple of 8 number. There is an other thing if you are reading and encrypting only bytes, (not converting it to Char64 as I did), then you need an appropriate PKCS5 or PKCS7 Padding, however in your case (due only 128bits and it's has been created with older versions of Android) PKCS5 would be enough, though you also should put it in your SecreteKeySpec something like "AES/CBC/PKCS5Padding" or "AES/ECB/PKCS5Padding" rather than just "AES", because Android 4.2 it's using PKCS7Padding as default and if it's only bytes you really need the same algorithm that was the default before. Try get a device with an Android earlier than 4.2 check the Object tree on your "keygen.init(128, secrand);" if I'm not mistaken it's has the label cipher, than use it.
Give it a try.
This is my base String:
String args ="oauth_consumer_key="+enc(consumerkey) +
"&oauth_nonce="+enc(generateNonce()) +
"&oauth_signature_method=HMAC-SHA1" +
"&oauth_timestamp="+ timestamp +
"&oauth_token="+enc(Home.consToken) +
"&oauth_verifier="+verifier+"&oauth_version=1.0";
String base ="POST&"+enc("https://api.linkedin.com/uas/oauth /accessToken") +"&"+ enc(args);
String signature =computeHmac(base,consumer_secret+"&"+secretToken);
This is my Header:
String header = "OAuth " +
"oauth_consumer_key=\""+ enc(consumerkey)+ "\"," +
"oauth_nonce=\""+ enc(generateNonce()) + "\"," +
"oauth_signature_method=\"HMAC-SHA1\"," +
"oauth_timestamp=\""+ timestamp + "\"," +
"oauth_token=\""+Home.consToken + "\"," +
"oauth_signature=\""+enc(signature)+"\","+
"oauth_verifier=\""+verifier +"\","+
"oauth_version=\""+1.0+"\"" ;
I am using following method to generate Signature:
public String computeHmac(String baseString, String key)
throws NoSuchAlgorithmException, InvalidKeyException, IllegalStateException, UnsupportedEncodingException
{
Mac mac = Mac.getInstance("HmacSHA1");
SecretKeySpec secret = new SecretKeySpec(key.getBytes(), mac.getAlgorithm());
mac.init(secret);
byte[] digest = mac.doFinal(baseString.getBytes());
byte[] result=Base64.encodeBase64(digest);
return new String(result);
}
while executing this code i am getting the following error...
oauth_problem=signature_invalid&
oauth_problem_advice=com.linkedin.security.auth.pub.LoginDeniedInvalidAuthTokenException
can anybody help me out this?
Thanks...
Femi's answer is absolutely correct, however, it wasn't obvious for me what exactly is intval(b). As i understood it's b & 0xFF.
Also I applied some optimizations (that I found here) and here is my code:
private static String hmacSha1(String value, String key)
throws UnsupportedEncodingException, NoSuchAlgorithmException,
InvalidKeyException {
String type = "HmacSHA1";
SecretKeySpec secret = new SecretKeySpec(key.getBytes(), type);
Mac mac = Mac.getInstance(type);
mac.init(secret);
byte[] bytes = mac.doFinal(value.getBytes());
return bytesToHex(bytes);
}
private final static char[] hexArray = "0123456789abcdef".toCharArray();
private static String bytesToHex(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
int v;
for (int j = 0; j < bytes.length; j++) {
v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
Messy, but I'm using this:
static String hash_hmac(String type, String value, String key)
{
try {
javax.crypto.Mac mac = javax.crypto.Mac.getInstance(type);
javax.crypto.spec.SecretKeySpec secret = new javax.crypto.spec.SecretKeySpec(key.getBytes(), type);
mac.init(secret);
byte[] digest = mac.doFinal(value.getBytes());
StringBuilder sb = new StringBuilder(digest.length*2);
String s;
for (byte b : digest){
s = Integer.toHexString(intval(b));
if(s.length() == 1) sb.append('0');
sb.append(s);
}
return sb.toString();
} catch (Exception e) {
android.util.Log.v("TAG","Exception ["+e.getMessage()+"]", e);
}
return "";
}
You then invoke it like this:
hash_hmac("HmacSHA1", value, key);