是的,加密可能是矫枉过正。但是,您不应因此而陷入陷阱并使用不太安全的加密原语。尤其是使用 CBC 模式安全性、静态盐等确实是您应该避免的错误。 XOR 或 DES 加密真是名不副实,这些加密方案分分钟就能破解。
如果加密在服务器性能方面过于夸张,这是一个只有您才能回答的问题。通常 IO 和复杂的 SQL 查询将比对几个字节数据进行简单的对称加密更多地为您的系统提供任务。
我将向您展示使用 GCM 改造的课程,并且 - 如果不可用或者如果 12 字节的标签大小开销太大 - CTR 模式加密。
只有在无法使用(随机)GUID 的情况下,您才应该这样做。
警告:我不会认为以下类有足够的输入参数检查(整数溢出等)。我在尝试实现所有这些检查和 JUnit 测试时感到无聊(无论如何都没有得到报酬:P)。在调用任何函数之前清理您的输入。
AES/GCM:
import static java.nio.charset.StandardCharsets.*;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
public class SecurityHelperGCM {
private static final int NONCE_SIZE = 8;
private static final int TAG_SIZE = 12;
// make sure that the hexadecimals represent a *truly random* byte array
// (e.g. use SecureRandom)
private final SecretKey STATIC_SECRET_KEY = new SecretKeySpec(
hexDecode("66e517bb5fd7df840060aed7e8b58986"), "AES");
private Cipher cipher;
private static byte[] hexDecode(final String hex) {
final byte[] data = new byte[hex.length() / 2];
for (int i = 0; i < data.length; i++) {
data[i] = (byte) Integer.parseInt(hex.substring(i * 2, i * 2 + 2),
16);
}
return data;
}
public SecurityHelperGCM() {
try {
this.cipher = Cipher.getInstance("AES/GCM/NoPadding");
} catch (final Exception e) {
e.printStackTrace();
}
}
private static int generateRandomNonce(final byte[] nonceBuffer,
final int offset, final int size) {
final SecureRandom rng = new SecureRandom();
final byte[] nonce = new byte[size];
rng.nextBytes(nonce);
System.arraycopy(nonce, 0, nonceBuffer, offset, size);
clearArray(nonce);
return offset + size;
}
private static void clearArray(final byte[] nonce) {
// clean up...
for (int i = 0; i < nonce.length; i++) {
nonce[i] = 0;
}
}
private static GCMParameterSpec generateGCMParametersFromNonce(
final byte[] nonceBuffer, final int offset, final int size,
final int blockSize) {
final GCMParameterSpec gcmParameters = new GCMParameterSpec(TAG_SIZE
* Byte.SIZE, nonceBuffer, offset, size);
return gcmParameters;
}
public String encrypt(final String secret) {
final byte[] plaintext = secret.getBytes(UTF_8);
final byte[] nonceAndCiphertext = new byte[NONCE_SIZE
+ plaintext.length + TAG_SIZE];
int offset = generateRandomNonce(nonceAndCiphertext, 0, NONCE_SIZE);
final GCMParameterSpec nonceIV = generateGCMParametersFromNonce(
nonceAndCiphertext, 0, NONCE_SIZE, this.cipher.getBlockSize());
try {
this.cipher.init(Cipher.ENCRYPT_MODE, this.STATIC_SECRET_KEY,
nonceIV);
offset += this.cipher.doFinal(plaintext, 0, plaintext.length,
nonceAndCiphertext, offset);
if (offset != nonceAndCiphertext.length) {
throw new IllegalStateException(
"Something wrong during encryption");
}
// Java 8 contains java.util.Base64
return DatatypeConverter.printBase64Binary(nonceAndCiphertext);
} catch (final GeneralSecurityException e) {
throw new IllegalStateException(
"Missing basic functionality from Java runtime", e);
}
}
public String decrypt(final String encrypted) throws BadPaddingException {
final byte[] nonceAndCiphertext = DatatypeConverter
.parseBase64Binary(encrypted);
final GCMParameterSpec nonceIV = generateGCMParametersFromNonce(
nonceAndCiphertext, 0, NONCE_SIZE, this.cipher.getBlockSize());
try {
this.cipher.init(Cipher.DECRYPT_MODE, this.STATIC_SECRET_KEY,
nonceIV);
final byte[] plaintext = this.cipher.doFinal(nonceAndCiphertext,
NONCE_SIZE, nonceAndCiphertext.length - NONCE_SIZE);
return new String(plaintext, UTF_8);
} catch (final BadPaddingException e) {
throw e;
} catch (final GeneralSecurityException e) {
throw new IllegalStateException(
"Missing basic functionality from Java runtime", e);
}
}
public static void main(final String[] args) {
final String secret = "owlstead";
final SecurityHelperGCM securityHelperGCM = new SecurityHelperGCM();
final String ct = securityHelperGCM.encrypt(secret);
String pt = null;
try {
pt = securityHelperGCM.decrypt(ct);
} catch (BadPaddingException e) {
System.out.println("Ciphertext tampered, take action!");
}
System.out.println(pt);
}
}
AES/CTR:
import static java.nio.charset.StandardCharsets.*;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
public class SecurityHelperCTR {
private static final int NONCE_SIZE = 8;
// make sure that the hexadecimals represent a *truly random* byte array
// (e.g. use SecureRandom)
private final SecretKey STATIC_SECRET_KEY = new SecretKeySpec(
hexDecode("66e517bb5fd7df840060aed7e8b58986"), "AES");
private Cipher cipher;
private static byte[] hexDecode(final String hex) {
final byte[] data = new byte[hex.length() / 2];
for (int i = 0; i < data.length; i++) {
data[i] = (byte) Integer.parseInt(hex.substring(i * 2, i * 2 + 2),
16);
}
return data;
}
public SecurityHelperCTR() {
try {
this.cipher = Cipher.getInstance("AES/CTR/NoPadding");
} catch (final Exception e) {
e.printStackTrace();
}
}
private static int generateRandomNonce(final byte[] nonceBuffer,
final int offset, final int size) {
final SecureRandom rng = new SecureRandom();
final byte[] nonce = new byte[size];
rng.nextBytes(nonce);
System.arraycopy(nonce, 0, nonceBuffer, offset, size);
return offset + size;
}
private static IvParameterSpec generateIVFromNonce(
final byte[] nonceBuffer, final int offset, final int size,
final int blockSize) {
final byte[] ivData = new byte[blockSize];
System.arraycopy(nonceBuffer, offset, ivData, 0, size);
final IvParameterSpec iv = new IvParameterSpec(ivData);
return iv;
}
public String encrypt(final String secret) {
final byte[] plaintext = secret.getBytes(UTF_8);
final byte[] nonceAndCiphertext = new byte[NONCE_SIZE
+ plaintext.length];
int offset = generateRandomNonce(nonceAndCiphertext, 0, NONCE_SIZE);
final IvParameterSpec nonceIV = generateIVFromNonce(nonceAndCiphertext,
0, NONCE_SIZE, this.cipher.getBlockSize());
try {
this.cipher.init(Cipher.ENCRYPT_MODE, this.STATIC_SECRET_KEY,
nonceIV);
offset += this.cipher.doFinal(plaintext, 0, plaintext.length,
nonceAndCiphertext, offset);
if (offset != nonceAndCiphertext.length) {
throw new IllegalStateException(
"Something wrong during encryption");
}
// Java 8 contains java.util.Base64
return DatatypeConverter.printBase64Binary(nonceAndCiphertext);
} catch (final GeneralSecurityException e) {
throw new IllegalStateException(
"Missing basic functionality from Java runtime", e);
}
}
public String decrypt(final String encrypted) {
final byte[] nonceAndCiphertext = DatatypeConverter
.parseBase64Binary(encrypted);
final IvParameterSpec nonceIV = generateIVFromNonce(nonceAndCiphertext,
0, NONCE_SIZE, this.cipher.getBlockSize());
try {
this.cipher.init(Cipher.DECRYPT_MODE, this.STATIC_SECRET_KEY,
nonceIV);
final byte[] plaintext = this.cipher.doFinal(nonceAndCiphertext,
NONCE_SIZE, nonceAndCiphertext.length - NONCE_SIZE);
// note: this may return an invalid result if the value is tampered
// with
// it may even contain more or less characters
return new String(plaintext, UTF_8);
} catch (final GeneralSecurityException e) {
throw new IllegalStateException(
"Missing basic functionality from Java runtime", e);
}
}
public static void main(final String[] args) {
final String secret = "owlstead";
final SecurityHelperCTR securityHelper = new SecurityHelperCTR();
final String ct = securityHelper.encrypt(secret);
final String pt = securityHelper.decrypt(ct);
System.out.println(pt);
}
}