2016-08-22 19:31:03 +02:00
|
|
|
package me.impy.aegis.crypto;
|
|
|
|
|
2016-11-13 18:21:00 +01:00
|
|
|
import java.io.ByteArrayOutputStream;
|
|
|
|
import java.io.IOException;
|
2017-08-18 22:12:45 +02:00
|
|
|
import java.lang.reflect.UndeclaredThrowableException;
|
2017-08-07 22:38:36 +02:00
|
|
|
import java.nio.ByteBuffer;
|
|
|
|
import java.nio.CharBuffer;
|
|
|
|
import java.nio.charset.Charset;
|
2016-11-13 18:21:00 +01:00
|
|
|
import java.security.InvalidAlgorithmParameterException;
|
|
|
|
import java.security.InvalidKeyException;
|
2017-08-18 22:12:45 +02:00
|
|
|
import java.security.MessageDigest;
|
2016-11-13 18:21:00 +01:00
|
|
|
import java.security.NoSuchAlgorithmException;
|
|
|
|
import java.security.SecureRandom;
|
|
|
|
import java.security.spec.InvalidKeySpecException;
|
2016-08-22 19:31:03 +02:00
|
|
|
import java.util.Arrays;
|
|
|
|
|
2016-11-13 18:21:00 +01:00
|
|
|
import javax.crypto.BadPaddingException;
|
|
|
|
import javax.crypto.Cipher;
|
|
|
|
import javax.crypto.IllegalBlockSizeException;
|
2017-05-03 21:08:38 +02:00
|
|
|
import javax.crypto.KeyGenerator;
|
2016-11-13 18:21:00 +01:00
|
|
|
import javax.crypto.NoSuchPaddingException;
|
|
|
|
import javax.crypto.SecretKey;
|
|
|
|
import javax.crypto.spec.GCMParameterSpec;
|
2017-08-07 22:38:36 +02:00
|
|
|
import javax.crypto.spec.SecretKeySpec;
|
|
|
|
|
|
|
|
import org.spongycastle.crypto.generators.SCrypt;
|
2016-11-13 18:21:00 +01:00
|
|
|
|
2016-08-22 19:31:03 +02:00
|
|
|
public class CryptoUtils {
|
2017-08-18 22:12:45 +02:00
|
|
|
public static final String CRYPTO_HASH = "SHA-256";
|
|
|
|
public static final byte CRYPTO_HASH_SIZE = 32;
|
|
|
|
|
2017-08-07 22:38:36 +02:00
|
|
|
public static final String CRYPTO_CIPHER_RAW = "AES/ECB/NoPadding";
|
2017-08-18 22:12:45 +02:00
|
|
|
public static final byte CRYPTO_KEY_SIZE = 32;
|
|
|
|
|
2017-08-07 22:38:36 +02:00
|
|
|
public static final String CRYPTO_CIPHER_AEAD = "AES/GCM/NoPadding";
|
2016-11-13 18:21:00 +01:00
|
|
|
public static final byte CRYPTO_TAG_SIZE = 16;
|
|
|
|
public static final byte CRYPTO_NONCE_SIZE = 12;
|
|
|
|
public static final byte CRYPTO_SALT_SIZE = 32;
|
|
|
|
|
2017-08-07 22:38:36 +02:00
|
|
|
public static final int CRYPTO_SCRYPT_N = 2 << 14;
|
|
|
|
public static final int CRYPTO_SCRYPT_r = 8;
|
|
|
|
public static final int CRYPTO_SCRYPT_p = 1;
|
|
|
|
|
|
|
|
public static SecretKey deriveKey(char[] password, byte[] salt, int n, int r, int p) throws NoSuchAlgorithmException, InvalidKeySpecException {
|
|
|
|
byte[] bytes = toBytes(password);
|
|
|
|
byte[] keyBytes = SCrypt.generate(bytes, salt, n, r, p, CRYPTO_KEY_SIZE);
|
|
|
|
zero(bytes);
|
|
|
|
SecretKey key = new SecretKeySpec(keyBytes, 0, keyBytes.length, "AES");
|
|
|
|
zero(keyBytes);
|
|
|
|
return key;
|
2016-11-13 18:21:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public static Cipher createCipher(SecretKey key, int opmode) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException {
|
|
|
|
byte[] nonce = generateNonce();
|
|
|
|
return createCipher(key, opmode, nonce);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static Cipher createCipher(SecretKey key, int opmode, byte[] nonce) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException {
|
|
|
|
GCMParameterSpec spec = new GCMParameterSpec(CRYPTO_TAG_SIZE * 8, nonce);
|
2017-05-03 21:08:38 +02:00
|
|
|
Cipher cipher = Cipher.getInstance(CRYPTO_CIPHER_AEAD);
|
2016-11-13 18:21:00 +01:00
|
|
|
cipher.init(opmode, key, spec);
|
|
|
|
return cipher;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static CryptResult encrypt(byte[] data, Cipher cipher) throws BadPaddingException, IllegalBlockSizeException {
|
|
|
|
// split off the tag to store it separately
|
|
|
|
byte[] result = cipher.doFinal(data);
|
|
|
|
byte[] tag = Arrays.copyOfRange(result, result.length - CRYPTO_TAG_SIZE, result.length);
|
|
|
|
byte[] encrypted = Arrays.copyOfRange(result, 0, result.length - CRYPTO_TAG_SIZE);
|
|
|
|
|
|
|
|
return new CryptResult() {{
|
|
|
|
Parameters = new CryptParameters() {{
|
|
|
|
Nonce = cipher.getIV();
|
|
|
|
Tag = tag;
|
|
|
|
}};
|
|
|
|
Data = encrypted;
|
|
|
|
}};
|
|
|
|
}
|
|
|
|
|
|
|
|
public static CryptResult decrypt(byte[] encrypted, Cipher cipher, CryptParameters params) throws IOException, BadPaddingException, IllegalBlockSizeException {
|
|
|
|
// append the tag to the ciphertext
|
|
|
|
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
|
|
|
stream.write(encrypted);
|
|
|
|
stream.write(params.Tag);
|
|
|
|
|
|
|
|
encrypted = stream.toByteArray();
|
|
|
|
byte[] decrypted = cipher.doFinal(encrypted);
|
|
|
|
|
|
|
|
return new CryptResult() {{
|
|
|
|
Parameters = params;
|
|
|
|
Data = decrypted;
|
|
|
|
}};
|
|
|
|
}
|
|
|
|
|
2017-08-18 22:12:45 +02:00
|
|
|
public static byte[] hashKey(SecretKey key) {
|
|
|
|
MessageDigest hash;
|
|
|
|
try {
|
|
|
|
hash = MessageDigest.getInstance(CRYPTO_HASH);
|
|
|
|
} catch (NoSuchAlgorithmException e) {
|
|
|
|
throw new UndeclaredThrowableException(e);
|
|
|
|
}
|
|
|
|
|
|
|
|
byte[] bytes = key.getEncoded();
|
|
|
|
hash.update(bytes);
|
|
|
|
CryptoUtils.zero(bytes);
|
|
|
|
return hash.digest();
|
|
|
|
}
|
|
|
|
|
2017-05-03 21:08:38 +02:00
|
|
|
public static SecretKey generateKey() throws NoSuchAlgorithmException {
|
|
|
|
KeyGenerator generator = KeyGenerator.getInstance("AES");
|
|
|
|
generator.init(CRYPTO_KEY_SIZE * 8);
|
|
|
|
return generator.generateKey();
|
|
|
|
}
|
|
|
|
|
2016-11-13 18:21:00 +01:00
|
|
|
public static byte[] generateSalt() {
|
|
|
|
return generateRandomBytes(CRYPTO_KEY_SIZE);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static byte[] generateNonce() {
|
|
|
|
return generateRandomBytes(CRYPTO_NONCE_SIZE);
|
|
|
|
}
|
|
|
|
|
|
|
|
private static byte[] generateRandomBytes(int length) {
|
|
|
|
SecureRandom random = new SecureRandom();
|
|
|
|
byte[] data = new byte[length];
|
|
|
|
random.nextBytes(data);
|
|
|
|
return data;
|
2016-08-22 19:31:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public static void zero(char[] data) {
|
|
|
|
Arrays.fill(data, '\0');
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void zero(byte[] data) {
|
2016-11-13 18:21:00 +01:00
|
|
|
Arrays.fill(data, (byte) 0);
|
2016-08-22 19:31:03 +02:00
|
|
|
}
|
2017-08-07 22:38:36 +02:00
|
|
|
|
|
|
|
private static byte[] toBytes(char[] chars) {
|
|
|
|
CharBuffer charBuf = CharBuffer.wrap(chars);
|
|
|
|
ByteBuffer byteBuf = Charset.forName("UTF-8").encode(charBuf);
|
2017-08-07 22:54:23 +02:00
|
|
|
return byteBuf.array();
|
2017-08-07 22:38:36 +02:00
|
|
|
}
|
2016-08-22 19:31:03 +02:00
|
|
|
}
|