Java Cryptography Extensions and Unlimited Strength - java

I am a total cryptography novice and was looking to have a simple (ha!) AESEncryption utility class that I could use for reading/writing files and string with AES keys. Something like:
String toEcnrypt = "This is a secret message!";
AESEcnryption aes = new AESEncryption(); // 256-bit by default
String encrypted = aes.encrypt(toEncrypt);
// Do some stuff
String shouldBeSameAsFirstString = aes.decrypt(encrypted);
The idea being that every time an AESEncryption is instantiated, a KeySpec is generated (and can be returned by the API for subsequent storage). Here's what I cooked up after examining the code of much, much brighter people than myself (so if you see your code here, thanks!):
public class AESEncryption {
private SecretKeySpec keySpec;
public AESEncryption()
{
super();
setKeySpec(AES256Encryption.generateAES256KeySpec());
}
// Uses 256-bit encryption by default.
public static SecretKeySpec generateAES256KeySpec()
{
// Stack variables
byte[] byteArray = new byte[16];
SecretKey oTmpKey = null;
KeyGenerator oKeyGen;
try
{
oKeyGen = KeyGenerator.getInstance("AES");
oKeyGen.init(256);
oTmpKey = oKeyGen.generateKey();
}
catch(Throwable oThrown)
{
throw new RuntimeException(oThrown);
}
byteArray = oTmpKey.getEncoded();
return new SecretKeySpec(byteArray, "AES");
}
public String encrypt(final String p_strPlaintext)
{
String strEncrypted = null;
try
{
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
strEncrypted = Base64.encodeBase64String(cipher
.doFinal(p_strPlaintext.getBytes()));
}
catch(Throwable oThrown)
{
System.out.println(oThrown.getMessage());
throw new RuntimeException(oThrown);
}
return strEncrypted;
}
}
For the Base64 En/Decoding I'm using Commons Codec - why? Because like I said I'm a crypto novice and that's the only thing I could find that seemed to get the job done!
When I use this code:
// This creates a unique key spec for this instance.
AESEncryption aes = new AESEncryption();
String toEncrypt = "blah";
// Throws a Throwable and prints the following to the console:
// "Illegal key size or default parameters"
String encrypted = aes.encrypt(toEncrypt);
I saw this question on SO where the asker had the same problem and I see that I may be missing the JCE. Knowing next to nothing about JCE, here's what I've collected:
The JCE is required for the AES algorithm to execute on the Java platform
The JCE downloads as a ZIP but really just contains two JARs
I put these 2 JARs (US_export_policy and local_policy) on my project's build path (Eclipse) and reran the code. Again the same problem. I know the linked article references installation instructions that recommended including these JARs in the JRE, but at runtime my app should only care about finding the JARs on the classpath - it shouldn't care about where it finds them on the classpath!
Is there anything I can do from inside Elcipse to make sure the JCE is available to my runtime classpath? Or am I way off base and have a bug in my code that is causing these errors?

You could simply use 128 bit AES keys. They are secure enough 99% of the time. Either that or use 256 bit keys and install the unlimited strength crypto files as indicated in the readme. If you could simply put them in the classpath everybody would simply copy the contents into their own libraries and skip the whole protection. They don't contain runnable code, just resources.

i'm pretty sure those jars are meaningless in the runtime classpath. they have to be installed in the jre installation dir.

Related

How to destroy SecretKey in Java 14?

I am trying to clear my Secretkey after decrypting.
From what I've read, SecretKeys can be destroyed via the destroy method since Java 8.
I am using Java 14 so it should be possible.
However, whenever I use the destroy method on a key, a DestroyFailedException is thrown.
I've also seen that people ignore that Exception in their code, however, if I were to do that, I am able to print the Key after calling the destroy method on it.
Here my Decryption method:
private byte[] decrypt(byte[] encryptedText, char[] password) throws InvalidKeyException,
InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchPaddingException,
InvalidKeySpecException, IllegalBlockSizeException, BadPaddingException, DestroyFailedException {
ByteBuffer bb = ByteBuffer.wrap(encryptedText);
byte[] iv = new byte[ivLengthByte];
bb.get(iv);
byte[] salt = new byte[saltLengthByte];
bb.get(salt);
byte[] cipherText = new byte[bb.remaining()];
bb.get(cipherText);
SecretKey key;
key = crypto.getAESKeyFromPassword(password, salt);
Cipher cipher;
cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(tagLengthBit, iv));
byte[] plainText = cipher.doFinal(cipherText);
Main.clearArray(password, null);
Main.clearArray(null, iv);
Main.clearArray(null, salt);
Main.clearArray(null, cipherText);
key.destroy();
cipher = null;
return plainText;
}
After calling the destroy method, I am, as said, (assuming I ignore the Exception) able to print the key via String encodedKey = Base64.getEncoder().encodeToString(key.getEncoded());
EDIT:
After using my Clear method on the array, I can still print it:
byte[] temp = key.getEncoded();
Main.clearArray(null, temp);
Clear Array:
protected static void clearArray(char[] chars, byte[] bytes) {
if (chars != null) {
for (int i = 0; i < chars.length; i++) {
chars[i] = '\0';
}
}
if (bytes != null) {
for (int i = 0; i < bytes.length; i++) {
bytes[i] = 0;
}
}
}
getAESKey:
protected SecretKey getAESKeyFromPassword(char[] password, byte[] salt)
throws NoSuchAlgorithmException, InvalidKeySpecException {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
KeySpec spec = new PBEKeySpec(password, salt, 65536, 256);
SecretKey secret = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
return secret;
}
Final Edit:
The best solution was to switch frim PBKDF2 to argon2.
https://github.com/kosprov/jargon2-api
Argon2 allows to use raw Hashes, then you may store that byte array in a SecureKeySpec as mentioned above, since it allows destroying of the Spec, and clear the raw Hash Array.
Actually, there is no easy solution to this. The problem is that the destroy method is an "optional" method. Not all implementations of SecretKey implement it. If you are using a SecretKey type that doesn't implement the method, you get this exception and there is no simple solution.
Unfortunately, you can't just implement the method yourself because (typically) the class it belongs on is provided by the Java SE libraries.
And even if you do figure out how to destroy the key, there is also the problem of the String containing the password1. (And this problem is more of a security risk, since trawling for a String containing a password is likely to be easier than trawling for an unknown byte sequence.)
Options:
Forget the problem. Don't destroy they key / password in memory. (See below for an explanation as to why this is not as bad as it sounds.)
Look for alternative JSSE crypto libraries where the SecretKey implementation for AES secret keys does implement destroy. I am guessing that the Bouncy Castle libraries might. (And if they don't, you always have the option of downloading the source code and patching them.)
Nasty reflection. You could figure out which actual class implements the secret key, and look at its code to work out how it represents the key internally. Then you could use reflection to break the abstraction and access its private state and ... write zeros over the key.
Why is not destroying the key not a disaster?
So some security experts may disagree with this, but I still think it is a valid viewpoint.
When you zero a key or password in memory, you are (ostensibly) protecting against the following kinds of attack:
Attaching a Java debugger to the JVM process and using that to locate and read the key.
Reading the JVM processes memory.
Reading memory pages that have been written to disk.
How easy are these attacks? Well the first two require that the hacker has already gotten into the host and escalated to (probably) root privilege. In the third case you could do it that way, but the hacker could also just steal the hard drive where the swap pages are written.
In all cases, the hacker then has to find the secret key. Unlike (say) a C / C++ program, the key won't be stored at a fixed location. Instead the hacker has to find it by pattern matching, or by finding reference chains. (A Java debugger would make it easier, provided that the key object is still reachable.) And the flip-side is once the key has been garbage collected, the copy in memory will be gone, and the copy in swap will go the next time that the OS writes out the (now) dirty page where the key object once lived. After that ... it is "gone" for all practical purposes.
So rewind a bit. I said that in order to carry off this kind of attack, the hacker already needs root access. (Or the hard drives, which most likely amounts to the same thing.) Now if they have that, there are other ways they can steal the secret key. For example:
Use the debugger to set a breakpoint on (say) the destroy method, and grab the key before it is destroyed.
Use the debugger to capture the password before you create the key.
Steal the private key for the server's SSL cert (or whatever) so that they can pick up the password from network traffic.
Install a software keystroke logger.
Replace your application code with a version that leaks the key or password via some side channel.
And of course they can install backdoors, etc. In short, if the hacker has compromised the system to the degree needed to pull off a "steal stuff out of memory" attack against a JVM, that is probably the least of your worries.
Now a security expert may say that it is "best practice" to have a layered defense against hackers. There is some truth to that. However, if security is that important to you, you should do a proper security analysis (not just a "tick the boxes" audit) and figure out what the real risks are. This will (probably2) tell you that it is better to focus on making the system secure than worrying if someone (with root privilege) can steal keys out of memory.
1 - Though not in your case, because I see that you are using a char[] ... which can be cleared. Except that this still vulnerable to all of the other attacks that I talk about.
2 - Or maybe it won't. But you need to do the analysis!
You must implement the destroy method yourself. The documentation explains this.
https://docs.oracle.com/javase/8/docs/api/javax/crypto/SecretKey.html
I may have found a solution, I tried using this: https://github.com/dbsystel/SecureSecretKeySpec
The only problem is that the key would have to be a byte array, and doing this:
protected SecureSecretKeySpec getAESKeyFromPassword(char[] password, byte[] salt)
throws NoSuchAlgorithmException, InvalidKeySpecException {
SecretKeyFactory factory= SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
KeySpec spec = new PBEKeySpec(password, salt, 65536, 256);
byte[] temp = factory.generateSecret(spec).getEncoded();
SecureSecretKeySpec sec= new SecureSecretKeySpec(temp, "AES");
Main.clearArray(null, temp);
return sec;
}
Might not be good, since there is a getEncoded called on the SecretKey, so there might be a SecretKey in the Memory?

How can I read a rsa public key form string(Generate by java and I want to read it with Python)

The code generate the public key is
public static final String CHARSET = "UTF-8";
public static final String RSA_ALGORITHM = "RSA";
public static Map<String, String> createKeys(int keySize){
KeyPairGenerator kpg;
try{
kpg = KeyPairGenerator.getInstance(RSA_ALGORITHM);
}catch(NoSuchAlgorithmException e){
throw new IllegalArgumentException("No such algorithm-->[" + RSA_ALGORITHM + "]");
}
kpg.initialize(keySize);
KeyPair keyPair = kpg.generateKeyPair();
Key publicKey = keyPair.getPublic();
String publicKeyStr = Base64.encodeBase64URLSafeString(publicKey.getEncoded());
Key privateKey = keyPair.getPrivate();
String privateKeyStr = Base64.encodeBase64URLSafeString(privateKey.getEncoded());
Map<String, String> keyPairMap = new HashMap<String, String>();
keyPairMap.put("publicKey", publicKeyStr);
keyPairMap.put("privateKey", privateKeyStr);
return keyPairMap;
}
public static RSAPublicKey getPublicKey(String publicKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKey));
RSAPublicKey key = (RSAPublicKey) keyFactory.generatePublic(x509KeySpec);
return key;
}
The key is then send to me, and I want to read it with python importKey(). But I always get the error "RSA key format is not supported".
The key is "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDGrLFBqubzi45M_yxs5Ps4XW3DIOeAo5x7Ca9EYmWAig3Rb3Efm2PCgipwNube2Ae5eUI5dYlQW32FSF81rw7vNdwfODDzITyWRPLEuVbBbkF5zD6kTxycqlVbH-uTyb95181jpY_XY6tmEOCZCq3mZhil9VA4ZvAoSBcJ8muXaQIDAQAB"
After searching with Google, I've try to add header "-----BEGIN RSA PUBLIC KEY-----" to it and nothing different.
You are using a 'URLsafe' base64 encoder, although not the standard Java one. 'URLsafe' uses different characters (for code values 62 and 63) and no padding, and no linebreaks. PEM format (which was designed long before URLsafe encoding existed, indeed over a year before URLs existed!) uses the traditional base64 characters (now associated mostly with MIME), with padding, and with linebreaks (every 64 characters). Although not all software checks for linebreaks; you don't say which Python crypto library you are using (there are several) so I can't check if it cares about this point.
In Java 8 up you can use Base64.getMimeEncoder() to handle most of these, but if you are stuck on older Java (see below) and/or some other library, you'll have to give details about it. You could convert the - _ characters to + /, add = padding to a multiple of 4, and add linebreaks if and as needed.
OTOH the Python libs I've looked at accept 'DER' (i.e. binary) as well as PEM, so you could just decode the base64 (many decoders can handle lack of padding and at least some can handle both charsets with or without specification) and use that as-is.
The publickey encoding used by Java (which it imprecisely calls "X.509") and also by OpenSSL and some other things is generic and includes an algorithm identifier, so the correct PEM labels are -----BEGIN PUBLIC KEY----- and ------END PUBLIC KEY----- (NO RSA).
You don't say what Java you use, but it apparently defaulted to 1024-bit for RSA, which is obsolete for several years and is no longer considered to provide adequate safety margin (although there are no open reports yet of actually breaking it). 2048 is now widely considered the minimum, and some applications or environments for various reasons use more. But deciding what crypto parameters to use (and indeed whether your application should even be using RSA, and if so which variant) are not programming questions and offtopic for SO; they belong on either crypto.SX (for the underlying principles) or security.SX (for applications).

Java Tokenization with Gradle

For my current project, I'm working on connecting to a MySQL database using Java.
I have a bit of code, in which I'm using Gradle to substitute sensitive database credentials into the .java file using ReplaceTokens to populate a new version of the file in the build directory and source that version (with the replaced "detokenized" values) to compile the .class file.
I do not anticipate anyone aside from the core dev team handling the source .java files, only the .war which contains the compiled .class files. However, from a peek at these .class files using vim, I can tell that the detokenized values are plainly visible in the compiled bytecode.
My question is, assuming a scenario in which my .class files could be retrieved from the server by a potentially malicious agent, is there any better method of tokenization that would give another layer of security to the database credentials?
For additional information, the MySQL DB is accessed through a socket only, so I do not expect a malicious agent could do anything with the DB credentials alone, but I would like to make it as difficult as possible to determine these credentials anyway.
Thank you for any advice! I'm still very new to Java and Gradle in general, but this project has already given me much insight into what can be done.
Here is some simple code that does base64 encoding / decoding
I am using Blowfish for the algo
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
public static String encrypt(String text) throws Exception
{
SecretKeySpec sksSpec = new SecretKeySpec(key.getBytes(), algo );
Cipher cipher = Cipher.getInstance(algo);
cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, sksSpec);
byte[] encrypt_bytes = cipher.doFinal(text.getBytes());
return new String( Base64.encodeBase64(encrypt_bytes) );
}
public static String decrypt(String encrypt_str) throws Exception
{
SecretKeySpec sksSpec = new SecretKeySpec(key.getBytes(), algo);
Cipher cipher = Cipher.getInstance(algo);
cipher.init(Cipher.DECRYPT_MODE, sksSpec);
return new String(cipher.doFinal( Base64.decodeBase64(encrypt_str.getBytes("UTF-8"))));
}

How to replace android.util.Base64 by org.apache.commons.codec.binary.Base64?

I'm migrating my native Android game to libGDX. So I can't access the Android libraries anymore and I'm trying to replace android.util.Base64 by org.apache.commons.codec.binary.Base64. (I need Base64's encodeToString and decode methods.)
Unfortunately, with the new package I get this error:
java.security.InvalidKeyException: Illegal key size (using the same 24-character-key as I did before).
Here at stackoverflow they say it's probably because "Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files 7" are missing. But if I use them, the users of my app have to install them, too.
Is there any easier solution? Why did it work before?
EDIT:
This is the code that leads to the InvalidKeyException:
javax.crypto.Cipher writer = Cipher.getInstance("AES/CBC/PKCS5Padding");
String keyOf24Chars = "abcdefghijklmnopqrstuvwx";
IvParameterSpec ivSpec = getIv();
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.reset();
byte[] keyBytes = md.digest(keyOf24Chars.getBytes("UTF-8"));
SecretKeySpec secretKey = new SecretKeySpec(keyBytes, "AES/CBC/PKCS5Padding");
// secretKey.getAlgorithm(): "AES/CBC/PKCS5Padding"
// secretKey.getFormat(): "RAW"
// secretKey.getEncoded().length: 32
writer.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec); // java.security.InvalidKeyException: Illegal key size
EDIT 2:
As explained in Maarten Bodewes' comment, Android has it's own implementation of the java and javax classes which apparently have no problem with 32 byte keys. After I have installed the "JCE Unlimited Strength Jurisdiction Policy Files 7" we are coming to the code that uses Base64 and causes this error: java.lang.NoSuchMethodError: org.apache.commons.codec.binary.Base64.encodeToString:
String valueToEncode = "xyz";
byte[] secureValue;
try {
secureValue = writer.doFinal(valueToEncode.getBytes("UTF-8"));
} catch (Exception e) {
throw new SecurePreferencesException(e);
}
Base64 base64 = new Base64();
String secureValueEncoded = base64.encodeToString(secureValue);
But this method does exist (in BaseNCodec which Base64 extends):
public String encodeToString(final byte[] pArray) {
return StringUtils.newStringUtf8(encode(pArray));
}
How can I make Android use this method?
EDIT 3:
Finally I solved my problem by writing an interface and then using my old Android code (when compiling for Android). Check this example for libGDX: Interfacing with platform specific code.
No, there isn't an easier solution. You could use 3DES instead of AES (which I presume you are using) but you would be downgrading your security, and still be incompatible with the previous code. Downgrading security of AES to 128 is a better idea, but the incompatibility issue won't go away.
If you are not using the encryption/decryption in a third party library (e.g. JSSE for SSL or XML encryption) then you could directly use the Bouncy Castle or Spongy Castle API's. So that means directly using AESBlockCipher + a mode of encryption. Bouncy Castle doesn't have these kind of limitations - they are part of the Oracle Cipher implementation.
It was working before because Android doesn't have these kind of restrictions while Java 7/8 SE does.

Reimplement AES encryption using third-party Java library without US law limitations

I've implemented AES encryption with certain task-specific parameters using standard Java tools and BouncyCastle provider for specific AES algorithm.
Here is the code:
private byte[] aesEncryptedInfo(String info) throws UnsupportedEncodingException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidParameterSpecException, InvalidAlgorithmParameterException, NoSuchProviderException {
Security.addProvider(new BouncyCastleProvider());
SecretKey secret = new SecretKeySpec(CUSTOMLONGSECRETKEY.substring(0, 32).getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
cipher.init(Cipher.ENCRYPT_MODE, secret, new IvParameterSpec(VECTOR_SECRET_KEY.getBytes()));
return cipher.doFinal(info.getBytes("UTF-8"));
}
In some environments this code requires special policy files. See related question: InvalidKeyException Illegal key size
My goal is to reimplement it using third-party library, ideally I would use bouncy castle which is already used as provider. The library should have no restictions of standard java policy files. In other words there should be no restrictions.
Please suggest in your answers how to reimplement it using BouncyCastle or other third-party library which can work without restrictions mentioned. Ideally I would see the code :-)
Thank you very much for reading!
After a delay I now happy to post a solution. Hope that someone can benefit from it because Bouncy Castle documentation is not filled with a lot of examples :-)
private byte[] aesEncryptedInfo(String info)
// Creating AES/CBC/PKCS7Padding cipher with specified Secret Key and Initial Vector
PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESEngine()), new PKCS7Padding());
cipher.init(true, new ParametersWithIV(new KeyParameter(CUSTOMLONGSECRETKEY.getBytes()), VECTOR_SECRET_KEY.getBytes()));
byte[] inputData = info.getBytes("UTF-8");
int outBlockSize = cipher.getOutputSize(inputData.length);
byte[] outputData = new byte[outBlockSize];
int outLength = cipher.processBytes(inputData, 0, inputData.length, outputData, 0);
outLength += cipher.doFinal(outputData, outLength);
if (outLength != outBlockSize) {
return Arrays.copyOf(outputData, outLength);
}
else {
return outputData;
}
}
By the way I found two differences between Java API and Bouncy Castle API:
1. Bouncy Castle uses composition of objects to create needed cipher. While Java API uses string to identify needed cipher.
2. BC encryption code slightly bigger, while Java API code is more compact.
The solution is full replacement for original Java API implementation - the proof is a custom unit test that I made.
Use the Bouncycastle lightweight crypto API directly, rather than through Java JCE interface. Bouncycastle includes its own crypto API accessible through various classes in org.bouncycastle.* packages. It also implements the JCE provider interface to make some of its crypto implementations available through standard JCE classes like Cipher, KeyGenerator, etc.
The cryptography policy restrictions are enforced by the JCE classes, not by bouncycastle. Therefore if you do not use these classes you'll will not encounter any restrictions. On the downside you will sacrifice some portability. To get started, take a look at the javadocs for the AESEngine class, and the rest of the javadocs for the bouncycastle.
Why isn't it possible to just add the necessary policy files?
That would be the easiest thing to do.
If you live in the US and you export your software to other (maybe "unallowed") countries, you will (theoretically) get trouble either way (including policy files/doing the encryption yourself).
If you live outside the US, why even bother about it, just include the policy files, no one cares.
No option for buying a toolkit? RSA BSAFE

Categories