In one of our internal software we're implementing a new API endpoint which must be accessed by external sources through the internet and then it must be secured in some way.
Since we're not allowed to use library as OAuth or public and private keys we choosed javax.crypto AES to crypt out a "custom authorisation token" in each external source by this way:
...
Key aesKey = new SecretKeySpec("API-KEY".getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, aesKey);
byte[] applicationIdEncrypted = cipher.doFinal(applicationId.getBytes());
...
The token contains a custom applicationId to identify on the other side who's contacting that endpoint.
Since we must perform an HTTP call, we're converting applicationIdEncrypted into a base64 String
String base64Encoded = Base64.getEncoder().encodeToString(applicationIdEncrypted);
ON THE OTHER SIDE
We're getting the header and decode it from base64
String base64Decoded = new String(Base64.getDecoder().decode(header));
But when attempting to perform the last operation
Key aesKey = new SecretKeySpec("API-KEY".getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, aesKey);
String headerDecoded = new String(cipher.doFinal(base64Decoded.getBytes())); //<- THIS
We got javax.crypto.BadPaddingException: Given final block not properly padded
Both base64Encoded and base64Decoded have the same value in both of the ends.
Attempting to perform the same operation in one of the ends (to not use the HTTP channel) no exception is thrown -but- a different headerDecoded is returned by the new String(cipher.doFinal(base64Decoded.getBytes()));
Looked for the bytes applicationIdEncrypted and base64Decoded.getBytes() and they're slightly different:
applicationIdEncrypted
[-28, -103, 107, 70, -112, 121, 4, -14, -80, -114, -14, 92, -81, -13, -128, 97]
base64Decoded.getBytes()
[-28, -103, 107, 70, 63, 121, 4, -14, -80, -114, -14, 92, -81, -13, -128, 97]
I read that maybe passing from bytes to String could be a loss of informations (maybe?) but I cannot figure it out why of this behaviours since both base64Encoded and base64Decoded have the SAME value in both cases and scenarios.
How can I achieve the passage of a "custom authorisation token" using only Java 1.7 javax.crypto libraries?
EDIT
The "API-KEY" is something like 02E30E6BE24BF1EA
As #James K Polk says, I had a mess with the thousand of String conversion so I managed to have a cleaner code first for a better comprehensive code.
ON THE CLIENT
Key aesKey = new SecretKeySpec("API-KEY".getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, aesKey);
byte[] applicationIdEncrypted = cipher.doFinal(applicationId.getBytes());
byte[] base64Encoded = Base64.getEncoder().encode(applicationIdEncrypted);
String out = new String(base64Encoded);
where out is the only one conversion in String and it's the HTTP header's payload.
ON THE OTHER SIDE
byte[] in = out.getBytes();
byte[] base64Decoded = Base64.getDecoder().decode(in);
Key aesKey = new SecretKeySpec("API-KEY".getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, aesKey);
byte[] applicationIdDecrypted = cipher.doFinal(base64Decoded);
String applicationId= new String(applicationIdDecrypted);
I had ONLY two conversion into String: out (the header's base64 value) and applicationId.
In this way I used to have the same applicationId value.
Related
I have a problem with encoding a password in java and nodejs.
After I encode the password, I get the same result with javacode and nodejs.
result = [69, 83, 53, 95, -77, -40, -124, 109, 38, -85, 91, -105, 72, -61, 84, 54].
Then I convert it into a buffer in nodejs.
When i use crypto. I have use a method:
const content = 'et'
key = buffer.from(result)
const iv= Buffer.alloc(0)
crypto.createCipheriv("aes-128-ecb", key, iv)
let encrypted = cipher.update(content, "utf8", "base64");
encrypted += cipher.final("base64"); //result: Rrx7BNH5l/miPfFbGgAkMA==
I get a result that is different from the javacode
My java code
String content = 'et'
SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] result = cipher.doFinal(content);
system.out.printLn(Base64.encode(encryptResult)) //result:TJ4Z8bGSoJKiRFHwE929mg==
I think This problem occurs when converting the bytes into a buffer. So if array don't have negative number I'll get the correct result.
Please help me any solution
SecretKey key = keyFactory.generateSecret(keySpec);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] ciphertext = cipher.doFinal(cleartext);
return bytes2String(ciphertext);
I am getting java.security.InvalidKeyException i.e. no IV set when one expected error at cipher.init(Cipher.DECRYPT_MODE, key).cleartext is a byte array resultant of base64 decoding of a string.What am i missing here ?
I'm no expert at this but, you are specifying CBC mode which requires an initialization vector (IV), which for AES is a 16-byte parameter.
private final static byte[] iv = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };
private static final IvParameterSpec ivspec = new IvParameterSpec(iv);
Then when calling init() method provide the IV when encrypting and decrypting.
cipher.init(Cipher.ENCRYPT_MODE, key, ivspec);
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivspec);
You should never use the above approach and let Java generate the random IV for you or provide a SecureRandom implementation.
The point in using a random, IV is to make sure that the same plain text doesn't encrypt to generate the same cipher text twice. That is the sole purpose of the IV.
Store the IV at the beginning of the encrypted text and while decrypting you know that the starting n digits are the IV.
When not providing IV it will by default generate a random IV. Source
If this cipher (including its underlying feedback or padding scheme) requires any random bytes (e.g., for parameter generation), it will get them using the SecureRandom implementation of the highest-priority installed provider as the source of randomness. (If none of the installed providers supply an implementation of SecureRandom, a system-provided source of randomness will be used.)
Using
private final static byte[] iv = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };
and
private static final IvParameterSpec ivspec = new IvParameterSpec(iv);
results in a "pad block corrupted error" for me. I resolved that the way below by modifying the init() step:
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(new byte[16]));
without using static final parameters.
I have a question about using Decrypt in AES. I wrote the same program that encrypts the text.
Here is my Decrypt class. (I use a 16 byte key).
public static byte[] decryptAES(String message) throws Exception
{
String secretKey = "JohnIsAwesome!1!";
SecretKeySpec key = new SecretKeySpec(secretKey.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, key);
return cipher.doFinal(message.getBytes());
}
Here is my main. The encrypt is working perfectly.
public static void main (String[] args) throws Exception
{
String text = "MySuperSecretPassword!";
//Ecrypt the Text, then print it out in an array
String encryptText = Arrays.toString(encryptAES(text));
System.out.println("Encrypted Message"+ encryptText);
//Decrypt the Text, then print it out in an array
String decryptText = Arrays.toString(decryptAES(text1));
System.out.println("Decrypted Message"+ decryptText);
}
Encrypt output:
Encrypted Message[16, 69, 84, 118, 68, -36, -67, 125, -86, -106, -4, 24, -59, -77, -41, -32, -37, 104, -44, -42, 112, 87, 87, 101, 28, 99, 60, -27, 34, -88, -17, -114]
If anyone has any ideas why the decryption would not work, It would be greatly appreciated. I've been banging my head against the wall on this one.
Thank you
Sorry, forgot to add my Encrypt class here as well.
public static byte[] encryptAES(String message) throws Exception
{
String secretKey = "JohnIsAwesome!1!";
SecretKeySpec key = new SecretKeySpec(secretKey.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, key);
return cipher.doFinal(message.getBytes());
}
Arrays.toString(byte[] a) "Returns a string representation of the contents of the specified array." It does not convert a byte array to a String. Instead try using:
new String(decryptAES(text1), "UTF-8");
I am using following code for encryption in Android project:
SecretKeyFactory kf = SecretKeyFactory.getInstance("DES");
String key = "abcdefg";
DESKeySpec keySpec = new DESKeySpec(key.getBytes());
SecretKey _key = kf.generateSecret(keySpec);
String xform = "DES";
Cipher cipher = Cipher.getInstance(xform);
byte[] IV = { 11, 22, 33, 44, 55, 66, 77, 88, 99, 18, 69, 17, 72, 94, 18, 30 };
IvParameterSpec ips = new IvParameterSpec(IV);
cipher.init(Cipher.ENCRYPT_MODE, _key, ips);
String plainText = "abcdeffdkflsdkf";
byte[] cipherText = cipher.doFinal(plainText.getBytes());
I write this encrypted data in file and I want to decrypt this file in my java project where I am using following code:
SecretKeyFactory kf = SecretKeyFactory.getInstance("DES");
String key = "abcdefg";
DESKeySpec keySpec = new DESKeySpec(key.getBytes());
SecretKey _key = kf.generateSecret(keySpec);
String xform = "DES";
Cipher cipher = Cipher.getInstance(xform);
byte[] IV = { 11, 22, 33, 44, 55, 66, 77, 88, 99, 18, 69, 17, 72, 94, 18, 30 };
IvParameterSpec ips = new IvParameterSpec(IV);
cipher.init(Cipher.DECRYPT_MODE, _key, ips);
String cipherText;
//cipher text is read from file.
byte[] plainText = cipher.doFinal(cipherText.getBytes());
But it's not working.
When we do not specify mode of operation(i.e. CBC/ECB) and padding method(i.e. PKCS5Padding/NoPadding) and only algorithm name is specified for getting instance of Cipher then what are the default values which Android and Java use?
Does Android uses CBC by default? As it doesn't give error for IV. If I specify IV in init method and don't specify mode in my java project, it throws exception as IV cannot be used for ECB.
Thanks.
Both Android and Java default to "AES/ECB/PKCS5Padding". Both your key and IV are not of the correct size (so the code should not even run!) and you forget to perform (character) encoding/decoding correctly. Especially the ciphertext should never be directly converted into a string, as unprintable characters are likely to be dropped or converted.
I'm trying to securely store a password in a database and for that I chose to store its hash generated using the PBKDF2 function. I want to do this using the bouncy castle library but I don't know why I cannot get it to work by using the JCE interface...
The problem is that generating the hash in 3 different modes:
1. using the PBKDF2WithHmacSHA1 secret key factory provided by sun
2. using the bouncy castle api directly
3. using the bouncy castle through JCE
results in 2 distinct values: one common to the first two and one for the third.
Here is my code:
//Mode 1
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec keyspec = new PBEKeySpec("password".toCharArray(), salt, 1000, 128);
Key key = factory.generateSecret(keyspec);
System.out.println(key.getClass().getName());
System.out.println(Arrays.toString(key.getEncoded()));
//Mode 2
PBEParametersGenerator generator = new PKCS5S2ParametersGenerator();
generator.init(PBEParametersGenerator.PKCS5PasswordToUTF8Bytes(("password").toCharArray()), salt, 1000);
KeyParameter params = (KeyParameter)generator.generateDerivedParameters(128);
System.out.println(Arrays.toString(params.getKey()));
//Mode 3
SecretKeyFactory factorybc = SecretKeyFactory.getInstance("PBEWITHHMACSHA1", "BC");
KeySpec keyspecbc = new PBEKeySpec("password".toCharArray(), salt, 1000, 128);
Key keybc = factorybc.generateSecret(keyspecbc);
System.out.println(keybc.getClass().getName());
System.out.println(Arrays.toString(keybc.getEncoded()));
System.out.println(keybc.getAlgorithm());
I know that PBKDF2 is implemented using HMAC SHA1 so that is why i chose as algorithm in the last method the "PBEWITHHMACSHA1" which i took from the bouncy castle java docs.
The output is the following:
com.sun.crypto.provider.SunJCE_ae
[-53, 29, 113, -110, -25, 76, 115, -127, -64, 74, -63, 102, 75, 81, -21, 74]
[-53, 29, 113, -110, -25, 76, 115, -127, -64, 74, -63, 102, 75, 81, -21, 74]
org.bouncycastle.jce.provider.JCEPBEKey
[14, -47, -87, -16, -117, -31, 91, -121, 90, -68, -82, -31, -27, 5, -93, -67, 30, -34, -64, -40]
PBEwithHmacSHA
Any ideas?
In short, the reason for the difference is that PBKDF2 algorithm in modes #1 and #2 uses PKCS #5 v2 scheme 2 (PKCS5S2) for iterative key generation, but the BouncyCastle provider for "PBEWITHHMACSHA1" in mode #3 uses the PKCS #12 v1 (PKCS12) algorithm instead. These are completely different key-generation algorithms, so you get different results.
More detail on why this is so and why you get different sized results is explained below.
First, when you're constructing a JCE KeySpec, the keyLength parameter only expresses "a preference" to the provider what key size you want. From the API docs:
Note: this is used to indicate the preference on key length for variable-key-size ciphers. The actual key size depends on each provider's implementation.
The Bouncy Castle providers don't appear to respect this parameter, judging from the source of JCEPBEKey, so you should expect to get a 160-bit key back from any BC provider which uses SHA-1 when using the JCE API.
You can confirm this by programmatically accessing the getKeySize() method on the returned keybc variable in your test code:
Key keybc = factorybc.generateSecret(keyspecbc);
// ...
Method getKeySize = JCEPBEKey.class.getDeclaredMethod("getKeySize");
getKeySize.setAccessible(true);
System.out.println(getKeySize.invoke(keybc)); // prints '160'
Now, to understand what the "PBEWITHHMACSHA1" provider corresponds to, you can find the following in the source for BouncyCastleProvider:
put("SecretKeyFactory.PBEWITHHMACSHA1",
"org.bouncycastle.jce.provider.JCESecretKeyFactory$PBEWithSHA");
And the implementation of JCESecretKeyFactory.PBEWithSHA looks like this:
public static class PBEWithSHA
extends PBEKeyFactory
{
public PBEWithSHA()
{
super("PBEwithHmacSHA", null, false, PKCS12, SHA1, 160, 0);
}
}
You can see above that this key factory uses the PKCS #12 v1 (PKCS12) algorithm for iterative key generation. But the PBKDF2 algorithm that you want to use for password hashing uses PKCS #5 v2 scheme 2 (PKCS5S2) instead. This is why you're getting different results.
I had a quick look through the JCE providers registered in BouncyCastleProvider, but couldn't see any key generation algorithms that used PKCS5S2 at all, let alone one which also uses it with HMAC-SHA-1.
So I guess you're stuck with either using the Sun implementation (mode #1 above) and losing portability on other JVMs, or using the Bouncy Castle classes directly (mode #2 above) and requiring the BC library at runtime.
Either way, you should probably switch to 160-bit keys, so you aren't truncating the generated SHA-1 hash unnecessarily.
I found a BC Crypto-Only method (actually from the cms package of BC) which works to produce a UTF-8 based password encoding. This way I can generate KDF output which is compatible to
http://packages.python.org/passlib/lib/passlib.hash.cta_pbkdf2_sha1.html#passlib.hash.cta_pbkdf2_sha1
private byte[] calculatePasswordDigest(char[] pass, byte[] salt, int iterations)
throws PasswordProtectionException
{
try
{
/* JCE Version (does not work as BC uses PKCS12 encoding)
SecretKeyFactory kf = SecretKeyFactory.getInstance("PBEWITHHMACSHA1","BC");
PBEKeySpec ks = new PBEKeySpec(pass, salt, iterations,160);
SecretKey digest = kf.generateSecret(ks);
return digest.getEncoded();
*/
PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator();
gen.init(PBEParametersGenerator.PKCS5PasswordToUTF8Bytes(pass), salt, iterations);
byte[] derivedKey = ((KeyParameter)gen.generateDerivedParameters(160)).getKey();
return derivedKey;
}
catch(Exception e)
{
LOG.error("Failed to strengthen the password with PBKDF2.",e);
throw new PasswordProtectionException();
}
}
PBKDF2WithHmacSHA1 is already supported in BouncyCastle 1.60
https://www.bouncycastle.org/specifications.html
Password Hashing and PBE
Test passed with OpenJDK Runtime Environment 18.9 (build 11.0.1+13):
Security.addProvider(new BouncyCastleProvider());
String password = "xrS7AJk+V6L8J?B%";
SecureRandom rnd = new SecureRandom();
int saltLength = 16;
int keyLength = 128;
int iterationCount = 10000;
byte[] salt = new byte[saltLength];
rnd.nextBytes(salt);
//SunJCE
SecretKeyFactory factorySun = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1", "SunJCE");
KeySpec keyspecSun = new PBEKeySpec(password.toCharArray(), salt, iterationCount, keyLength);
SecretKey keySun = factorySun.generateSecret(keyspecSun);
System.out.println(keySun.getClass().getName());
System.out.println(Hex.toHexString(keySun.getEncoded()));
//BouncyCastle
SecretKeyFactory factoryBC = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1", "BC");
KeySpec keyspecBC = new PBEKeySpec(password.toCharArray(), salt, iterationCount, keyLength);
SecretKey keyBC = factoryBC.generateSecret(keyspecBC);
System.out.println(keyBC.getClass().getName());
System.out.println(Hex.toHexString(keyBC.getEncoded()));
Assert.assertArrayEquals(keySun.getEncoded(), keyBC.getEncoded());
The output is:
com.sun.crypto.provider.PBKDF2KeyImpl
e9b01389fa91a6172ed6e95e1e1a2611
org.bouncycastle.jcajce.provider.symmetric.util.BCPBEKey
e9b01389fa91a6172ed6e95e1e1a2611