I'm trying to create an application for Android that uses encryption to save user information and I cannot figure out what I'm doing wrong. I'm trying to create an instance of an AES cipher but the application keeps on throwing "InvalidKeyExceptions." Consider the following code:
public static final byte[] IV = new byte[]
{ 0x04, 0x08, 0x15, 0x16, 0x23, 0x42, 0x00, 0x00, 0x00, 0x00,0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f};
protected final IvParameterSpec params = new IvParameterSpec(IV);
protected Cipher myCipher;
public AESEncryptor(String passwd, InputStream source, String destinationFile)
{
try
{
myCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
Log.d("System.out.println", "Block Size: "+myCipher.getBlockSize());
myCipher.init(Cipher.ENCRYPT_MODE, AESEncryptor.generateSecretKeyFromPassword(passwd),params);
}
catch (Exception e)
{
e.printStackTrace();
}
}
I get this exception:
java.security.InvalidKeyException:
initialisation vector must be the same
length as block size..
The myCipher.init(...) line triggers this exception.
I understand what it's saying but according to myCipher.getBlockSize() the IV byte array should hold 16 bytes, and it does, but it doesn't work. I have also tried byte arrays of length 0-128, and nothing in that range works either.
Oh also, if I take this code, unaltered, and add it to a regular Java application, I get no errors. Compiling for Android seems to be causing this error.
Please help.
Thanks,
Ryan
Have you tried specifying explicitly the block size in your mode parameter?
Ex:
Cipher.getInstance("AES/CBC16/PKCS5Padding");
I noticed here that if you don't specify the block size then it is provider dependent.
Related
I'm trying to decrypt payload using SymmetricKey. I've tried ChaChaPoly and AES.GCM to open sealedBox but I'm still getting CryptoKit.CryptoKitError.authenticationFailure
here is my implementation:
let iv: [UInt8] = [0x00, 0x01, 0x02, 0x03,
0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B,
0x0C, 0x0D, 0x0E, 0x0F]
func generatePair() {
let priv = P256.KeyAgreement.PrivateKey()
privateKey = priv
publicKey = priv.publicKey
}
func createSymmetricKey(serverPublicKeyPEM: String) -> SymmetricKey? {
guard let privateKey = privateKey,
let publicKey = publicKey else { return nil }
do {
let serverPubKey = try P256.KeyAgreement.PublicKey(pemRepresentation: serverPublicKeyPEM)
let shared = try privateKey.sharedSecretFromKeyAgreement(with: serverPubKey)
let symetricKey = shared.hkdfDerivedSymmetricKey(using: SHA256.self,
salt: Data(bytes: iv, count: iv.count),
sharedInfo: publicKey.rawRepresentation + serverPubKey.rawRepresentation,
outputByteCount: 32)
return symetricKey
} catch {
//TODO: Handle Error
print("error \(error)")
return nil
}
}
func decrypt(payload: String, symmetricKey: SymmetricKey) {
guard let cipherText = Data(base64Encoded: payload) else { return }
do {
// let sealedBox = try ChaChaPoly.SealedBox(combined: cipherText)
// let decrypted = try ChaChaPoly.open(sealedBox, using: symmetricKey)
let sb = try AES.GCM.SealedBox(combined: cipherText)
let decrypted = try AES.GCM.open(sb, using: symmetricKey)
print("")
} catch {
print("error: \(error)") //here getting CryptoKit.CryptoKitError.authenticationFailure
}
}
Also I know how implementation on backend side looks like:
public static String encrypt(String sessionKey, String devicePublicKey, String plainString) throws Exception {
byte[] plain = Base64.getEncoder().encodeToString(plainString.getBytes(StandardCharsets.UTF_8)).getBytes();
SecretKey key = generateSharedSecret(decodePrivateKey(sessionKey), decodePublicKey( devicePublicKey));
Cipher encryptor = Cipher.getInstance("AES/CTR/NoPadding", BouncyCastleProvider.PROVIDER_NAME);
IvParameterSpec ivSpec = new IvParameterSpec(INITIALIZATION_VECTOR);
encryptor.init(Cipher.ENCRYPT_MODE, key, ivSpec);
return Base64.getEncoder().encodeToString(encryptor.doFinal(plain, 0, plain.length));
}
The issue probably lies within the initialisation vector or nonce you use. Counting the bytes, we come to a total of 16 nonce bytes, even though GCM only needs 12. Now, using 16 is not necessarily good or bad, but the CryptoKit implementation assumes 12 bytes when calling AES.GCM.SealedBox(combined:). In order to support 16 nonce bytes, you will have to use AES.GCM.SealedBox(nonce:ciphertext:tag:) instead.
let ciphertext = Data(...)
do {
let nonce = try AES.GCM.Nonce(data: ciphertext[0 ..< 16]
let message = ciphertext[16 ..< ciphertext.count - 16]
let tag = ciphertext[ciphertext.count - 16 ..< ciphertext.count]
let sb = try AES.GCM.SealedBox(nonce: nonce, ciphertext: ciphertext, tag: tag)
let decrypted = try AES.GCM.open(sb, using: key)
} catch {
print("Error: \(error)")
}
Looking at your server code, make sure the shared secret is not 'just' the shared secret. generateSharedSecret sounds like it's the secret after performing key exchange, but without performing the key derivation (HKDF, as seen in the Swift code).
Also looking deeper into your server code, make sure your response data contains the nonce, encrypted message and tag. Some crypto implementations force you to do this concatenation yourself. So instead of return Base64(doFinal) (pseudo code), you should instead make sure doFinal includes a tag (GCM only), and return Base64(nonce + encrypted message + tag). Again, the tag only when using GCM.
As also mentioned in the comments, GCM and CTR are different modes of operation for AES. Make sure you use the same one on both parties, so either GCM on both iOS and server or CTR on both iOS and server. Not doing so, will always result in failure of decryption.
If you want to use CTR, you'll have to take a look at CommonCrypto, the old Apple crypto library. This one has CTR implemented, but doesn't support GCM (as the implementation was never released).
One final note, when using GCM, also make sure your additional authentication data (if any) is correct.
I found on Google this code for encrypt/decrypt a string in Java:
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
byte[] input = "test".getBytes();
byte[] keyBytes = new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17 };
SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding", "BC");
System.out.println(new String(input));
// encryption pass
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] cipherText = new byte[cipher.getOutputSize(input.length)];
int ctLength = cipher.update(input, 0, input.length, cipherText, 0);
ctLength += cipher.doFinal(cipherText, ctLength);
System.out.println(new String(cipherText));
System.out.println(ctLength);
// decryption pass
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] plainText = new byte[cipher.getOutputSize(ctLength)];
int ptLength = cipher.update(cipherText, 0, ctLength, plainText, 0);
ptLength += cipher.doFinal(plainText, ptLength);
System.out.println(new String(plainText));
System.out.println(ptLength);
And this is the output (screenshot because I can't copy-paste some characters):
output screenshot
My question is:
Why the first input "test" is different from the second (decrypted) "test"?
I need this code to encrypt a password and save it on a TXT file and then read this encrypted password from the TXT file and decrypt it..
But if these two outputs are different I can't do this.
Second question:
Is it possible to exclude ";" from the encrypted text?
Can someone help me, please? Thanks!
If you read the documentation for getOutputSize() then you will see that it returns the maximum amount of plaintext to expect. The cipher instance cannot know how much padding is added, so it guesses high. You will have to resize the byte array when you are using ECB or CBC mode (or any other non-streaming mode).
System.out.println(ctLength);
As you can see, ctLength does have the correct size. Use Arrays.copyOf(plainText, ptLength) to get the right number of bytes, or use the four parameter String constructor (new String(plainText, 0, ptLength, StandardCharsets.UTF_8)) in case you're just interested in the string.
The ciphertext consists of random characters. It actually depends on your standard character set what you see on the screen. If you really need text, then you can base 64 encode the ciphertext.
ECB mode encryption is not suitable to encrypt strings. You should try and use a different mode that includes setting / storing an IV.
I'd use new String(StandardCharsets.UTF_8) and String#getBytes(StandardCharsets.UTF_8) to convert to and from strings. If you don't specify the character set then it uses the system default character set, and that means decrypting your passwords won't work on all systems. The allowed characters also differ with Linux & Android defaulting on UTF-8 while Java SE on Windows (still?) defaults to the Windows-1252 (extended Western-Latin) character set.
There is absolutely no need to use the Bouncy Castle provider for AES encryption (the compatible padding string is "PKCS5Padding").
Please don't grab random code samples from Google. You need to understand cryptography before you start implementing it. The chances that you grab a secure code sample is practically zero unfortunately.
I used sample code provided on this site by user. I need to encrypt password and store in file for future use. When user try to use system, I fetch password from file, decrypt it and use it for further processing. So I can't use different key during each encryption/decryption request. So I use fixed byte[] to store key instead of calling KeyGenerator.generateKey(). Below is full code.
public class App
{
static byte[] seckey=null;
static
{
try
{
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128);
// Generate the secret key specs.
// SecretKey skey = kgen.generateKey();
// seckey = skey.getEncoded();
// above won't work as can't generate new secret key for decrypt. Have to use same key for encrypt and decrypt
// seckey = new byte[]{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f};
seckey = new byte[]{(byte)172,(byte)236,(byte)125,(byte)222,(byte)188,(byte)33,(byte)210,(byte)4,(byte)202,(byte)31,(byte)188,(byte)152,(byte)220,(byte)104,(byte)62,(byte)64};
} catch (NoSuchAlgorithmException e)
{
e.printStackTrace();
}
}
public static void main( String[] args )
{
String password = encrypt("A123456"); //working
System.out.println(password);
System.out.println(decrypt(password));
String password = encrypt("A*501717"); //NOT working
System.out.println(password);
System.out.println(decrypt(password));
}
public static String encrypt(String passwd)
{
SecretKeySpec key = new SecretKeySpec(seckey, "AES");
byte[] output;
try
{
Cipher cipher = Cipher.getInstance("AES");
// encryption pass
cipher.init(Cipher.ENCRYPT_MODE, key);
output = cipher.doFinal(passwd.getBytes());
} catch (Exception e)
{
System.out.println("Unable to encrypt password.");
output = "".getBytes();
}
return new String(output);
}
public static String decrypt(String passwd)
{
if (!StringUtils.isNotBlank(passwd))
return "";
SecretKeySpec key = new SecretKeySpec(seckey, "AES");
byte[] output;
try
{
Cipher cipher = Cipher.getInstance("AES");
// decryption pass
cipher.init(Cipher.DECRYPT_MODE, key);
output = cipher.doFinal(passwd.getBytes());
} catch (Exception e)
{
System.out.println("Unable to decrypt password");
output = "".getBytes();
}
return new String(output);
}
}
The issue is, it works for most of the time but for certain character sequence it is faling to decrypt. e.g. presently not working for A123456. Then I changed secret key to below
seckey = new byte[]{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f};
after that it worked for A123456 but then failed for A*3qwe (which worked with earlier secret key. So I am totally not able to understand why is it not working for only some data?
Could someone help me please where am I doing wrong?
I resolved this issue by using Base64Encoder. Basically encrypted data can have some characters not possible to store in normal txt file and so decryption will fail. So I used Base64Encoder to encode encrypted string. This encoded characters are normal ascii characters and can be easily stored in txt file. Reverse thing is required to get original data i.e. first decode and then decrypt the string stored in file.
I'm having some weird problems with using a public key in a Java client. I get the public key from a C server, and I know the following: B64Encoded, X.509 RSA key, and I should use PKCS1Padding. I have done the following so far to make sure that the data being transmitted between the client and server is correct:
1) The data content and length that are sent from the server is identical with the java client
2) Once it's B64decoded, the data and length are the same
3) I've also verified that the data is the same between a C client and the Java client, until I start to create a public key from the decoded data.
I'm running into a problem, that the Java client is sending too much data back to server after the Cipher encrypts the password. I've tried to use different methods in creating the public key object, but nothing seems to work. I either get a "algid parse error, not a sequence"-error, or the key is created, and I end up sending too many bytes of data (139 from Java vs 128 from the C client).
Here's the code (snipped out some irrelevant things). Everything is broken down to single try/catches, as I was trying to pinpoint the problem:
byte[] pk = getKeyFromServer(); // 191 bytes
String keyString = new String(pk);
byte[] decoded = decode(keyString); // 139 bytes
try {
keySpec = new X509EncodedKeySpec(decoded);
} catch (Exception e) { e.printStackTrace();}
try {
keyFactory = KeyFactory.getInstance("RSA", "IBMJCE");
} catch (Exception e) { e.printStackTrace();}
try {
publicKey = (RSAPublicKey) keyFactory.generatePublic(keySpec);
} catch (Exception e) { e.printStackTrace();}
try {
cipher = Cipher.getInstance("RSA/SSL/PKCS1Padding", "IBMJCE");
} catch (Exception e) { e.printStackTrace(); }
try {
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
} catch (Exception e) { e.printStackTrace(); }
try { sendEncrypted(cipher.doFinal(pwd.getBytes()));
} catch (Exception e) { e.printStackTrace(); }
The above code runs into the exception I mentioned before, and if I remove the KeySpec and KeyFactory parts, and go with the following I get to the encryption part, but then I send too much data, and the server can't decrypt it (I got the exponent from digging around).
java.math.BigInteger modulus = new java.math.BigInteger(decoded);
java.math.BigInteger exponent = new java.math.BigInteger("22111");
publicKey = new RSAPublicKey(modulus, exponent);
So I guess the question is, am I doing something totally wrong, or is there something that I should know more about what the C client is doing to the BASE64decoded public key before it uses it to encrypt, and try to replicate that? Right now I am unable to access that part :(
I've tried to use different KeySpecs, PublicKeys and paddings, but the result is always the same (though the exception might differ when I'm way off base with the KeySpec).
The communication between the server and client work well otherwise. It's just this part that uses the public key that isn't working.
EDIT: Just wanted to add the public key String, if that tells anything:
Public key from server:
MIGIAoGBAMZawVoP6mHl0xD3Epn1l4S/6Ke20ZTNLKaXyi425NwhiE7LoTysAcpx
y3i9LXLVNGpdPNtpGD3mvlNJc/HfGQQ7NQpNyKpe5EzsODb1YCbODtfmaRODDW9B
qsrE8DCxI0g8gzu3NJTrUh4NfRaSBn9HaOnBUwiyQyihq2I6MB6bAgJWXw==
Could you try "RSA/ECB/PKCS1Padding" without the "/SSL" in the middle? SSL uses a special structure if I'm not mistaken.
In addition:
The encoding is not X509 compatible! It seems that the encoding of the key is PKCS#1 and X509 uses a small wrapper around that encoding!
From PKCS#1:
RSAPublicKey ::= SEQUENCE {
modulus INTEGER, -- n
publicExponent INTEGER -- e
}
From X509EncodedKeySpec:
SubjectPublicKeyInfo ::= SEQUENCE {
algorithm AlgorithmIdentifier,
subjectPublicKey BIT STRING }
So you will have to add the sequence tag, length, algorithm identifier and BIT STRING tag and length in front of the data you are receiving! Weird that you could let it do anything at all.
For this specific keylength and (weird) two byte public exponent, the header would consist of the following bytes:
(byte) 0x30, (byte) 0x81, (byte) 0x9E, (byte) 0x30, (byte) 0x0D, (byte) 0x06, (byte) 0x09, (byte) 0x2A,
(byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xF7, (byte) 0x0D, (byte) 0x01, (byte) 0x01, (byte) 0x01,
(byte) 0x05, (byte) 0x00, (byte) 0x03, (byte) 0x81, (byte) 0x8C, (byte) 0x00,
Alternatively, you could also simply retrieve the two integers from the structure and create an RSAPublicKeySpec from those.
I have a file encrypted using the following code in c:
unsigned char ckey[] = "0123456789ABCDEF";
unsigned char iv[8] = {0};
AES_set_encrypt_key(ckey, 128, &key);
AES_ctr128_encrypt(indata, outdata, 16, &key, aesstate.ivec, aesstate.ecount, &aesstate.num);
I have to decrypt this file using java so I was using the code below to do it:
private static final byte[] encryptionKey = new byte[]{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F };
byte[] iv = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
IvParameterSpec ips = new IvParameterSpec(iv);
Cipher aesCipher = Cipher.getInstance("AES/CTR/NoPadding");
SecretKeySpec aeskeySpec = new SecretKeySpec(encryptionKey, "AES");
aesCipher.init(Cipher.DECRYPT_MODE, aeskeySpec, ips);
FileInputStream is = new FileInputStream(in);
CipherOutputStream os = new CipherOutputStream(new FileOutputStream(out), aesCipher);
copy(is, os);
os.close();
The JAVA code doesn't give me any error but the output is not correct.
What am I doing wrong?
My main doubts are if i'm using the correct padding (also tried PKCS5Padding without success) and if the key and iv are correct (don't know what the function AES_set_encrypt_key really does...).
** EDIT **
I think I have an answer to my own question, but I still have some doubts.
CTR means counter mode. The function AES_ctr128_encrypt receives as parameters the actual counter (ecount) and the number of blocks used (num).
The file is being encrypted in blocks of 16 bytes, like this:
for(int i = 0; i < length; i+=16)
{
// .. buffer processing here
init_ctr(&aesstate, iv); //Counter call
AES_ctr128_encrypt(indata, outdata, 16, &key, aesstate.ivec, aesstate.ecount, &aesstate.num);
}
the function init_ctr does this:
int init_ctr(struct ctr_state *state, const unsigned char iv[8])
{
state->num = 0;
memset(state->ecount, 0, 16);
memset(state->ivec + 8, 0, 8);
memcpy(state->ivec, iv, 8);
return 0;
}
This means that before every encryption/decryption the C code is resetting the counter and the ivec.
I am trying to decrypt the file as a whole in java. This probably means Java is using the counter correctly but the C code is not as it is resetting the counter at each block.
Is my investigation correct?
I have absolutely NO CONTROL over the C code that is calling openssl. Is there a way of doing the same in JAVA, i.e. resetting the counter at each block of 16? (The API only requests the key, algorithm, mode and IV)
My only other option is to use openssl via JNI but I was trying to avoid it...
Thank you!
I did not try it, but you should be able to effectively emulate what is done there on the C side - decrypt each 16-byte (=128 bit) block separately, and reset the cipher between two calls.
Please note that using CTR mode for just one block, with a zero initialization vector and counter, defeats the goal of CTR mode - it is worse than ECB.
If I see this right, you could try to encrypt some blocks of zeros with your C function (or the equivalent Java version) - these should come out as the same block each time. XOR this block with any ciphertext to get your plaintext back.
This is the equivalent to a Caesar cipher on a 128-bit alphabet (e.g. the 16-byte blocks), the block cipher adds no security here to a simple 128-bit XOR cipher. Guessing one block of plaintext (or more generally, guessing 128 bits at the right positions, not necessary all in the same block) allows getting the effective key, which allows getting all the remaining plaintext blocks.
Your encryption keys are different.
The C code uses the ASCII character codes for 0 through F, whereas the Javacode uses the actual bytes 0 through 16.
There are numerous serious problems with that C code:
As already noted, it is reinitialising the counter on every block. This makes the encryption completely insecure. This can be fixed by calling init_ctr() once only, prior to encrypting the first block.
It is setting the IV statically to zero. A fresh IV should be generated randomly, for example if (!RAND_bytes(iv, 8)) { /* handle error */ }.
The code appears to be directly using a password string as the key. Instead, a key should be generated from the password using a key derivation function like PBKDF2 (implemented in OpenSSL by PKCS5_PBKDF2_HMAC_SHA1()).