This answer adds a bit more explanation to @sanketshah's great answer.
The following code shows the usage:
import org.identityconnectors.common.security.GuardedString;
import java.security.SecureRandom;
public class Main {
public static void main(String[] args) {
/*
* Using:
* "password".toCharArray();
* would create an immutable String "password",
* which remains in memory until GC is called.
*/
char[] password = new char[]{'p', 'a', 's', 's', 'w', 'o', 'r', 'd'};
GuardedString guardedString = new GuardedString(password);
/*
* Securely wipe the char array by storing random values in it.
* Some standards require multiple rounds of overwriting; see:
* https://en.wikipedia.org/wiki/Data_erasure#Standards
*/
SecureRandom sr = new SecureRandom();
for (int i = 0; i < password.length; i++)
password[i] = (char) sr.nextInt(Character.MAX_VALUE + 1);
//noinspection UnusedAssignment
password = null;
/*
* At some later point in the code, we might need the secret.
* Here's how to obtain it using Java 8+ lambdas.
*/
guardedString.access(chars -> {
for (char c : chars) {
System.out.print(c);
}
});
}
}
GuardedString
can be obtained from maven IdentityConnectors: Framework. However, for the actual implementation, one also needs IdentityConnectors: Framework Internal.
To be more precise, the former package defines the Encryptor
interface:
package org.identityconnectors.common.security;
/**
* Responsible for encrypting/decrypting bytes. Implementations
* are intended to be thread-safe.
*/
public interface Encryptor {
/**
* Decrypts the given byte array
* @param bytes The encrypted bytes
* @return The decrypted bytes
*/
public byte [] decrypt(byte [] bytes);
/**
* Encrypts the given byte array
* @param bytes The clear bytes
* @return The ecnrypted bytes
*/
public byte [] encrypt(byte [] bytes);
}
which is implemented by EncryptorImpl
in the second package. (The same goes for the abstract class EncryptorFactory
, which is extended by EncryptorFactoryImpl
).
The EncryptorFactory
actually fixes its implementation:
// At some point we might make this pluggable, but for now, hard-code
private static final String IMPL_NAME = "org.identityconnectors.common.security.impl.EncryptorFactoryImpl";
An insecure aspect of the implementations is that they use AES/CBC/PKCS5Padding
with hard-coded IV and key. The constructor of EncryptorFactoryImpl
passes true
to EncryptorImpl
:
public EncryptorFactoryImpl() {
_defaultEncryptor = new EncryptorImpl(true);
}
which causes it to use the default key. Regardless of that, the IV is always fixed:
public EncryptorImpl( boolean defaultKey ) {
if ( defaultKey ) {
_key = new SecretKeySpec(_defaultKeyBytes,ALGORITHM);
_iv = new IvParameterSpec(_defaultIvBytes);
}
else {
try {
_key = KeyGenerator.getInstance(ALGORITHM).generateKey();
_iv = new IvParameterSpec(_defaultIvBytes);
}
catch (RuntimeException e) {
throw e;
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
}
There is some space for improvements here:
- Use AES/CTR or AES/GCM instead of AES/CBC. (See block cipher mode of operation.)
- Always use random IV and key.
GuardedString
uses an internal method SecurityUtil.clear()
to clear byte arrays, which zeros out the bytes. It would be nice to have other possible data erasure algorithms.