Java 隐藏主键的加密性能
Java encryption performance to hide primary keys
我需要从最终用户输出中隐藏某些原始值(主要是主键,每个请求可能大约有 100 个),这样数据库就不容易被探测或迭代。加密速度比值的实际不可渗透性更重要。
我目前在以下包装器中使用 PBEWithMD5AndDES
- 这是不是太过分了?我刚开始使用 Java 作为 Web 后端。
public class SecurityHelper {
private static final String DEFAULT_KEY = "some-key-here";
private SecretKey secretKey;
private PBEParameterSpec parameterSpec;
private Cipher cipher;
public SecurityHelper() {
try {
char[] moduleKeyChars = DEFAULT_KEY.toCharArray();
KeySpec keySpec = new PBEKeySpec(moduleKeyChars);
secretKey = SecretKeyFactory.getInstance(
"PBEWithMD5AndDES").generateSecret(keySpec);
parameterSpec = new PBEParameterSpec(new byte[8], 1);
cipher = Cipher.getInstance("PBEWithMD5AndDES");
} catch (Exception e) {
e.printStackTrace();
}
}
public String encrypt(String secret) {
String encrypted = null;
try {
cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec);
byte[] stateBytes = cipher.doFinal(secret.getBytes("UTF-8"));
encrypted = DatatypeConverter.printBase64Binary(stateBytes);
} catch (Exception e) {
e.printStackTrace();
}
return encrypted;
}
public String decrypt(String encrypted) {
String decrypted = null;
try {
cipher.init(Cipher.DECRYPT_MODE, secretKey, parameterSpec);
byte[] stringBinary = DatatypeConverter.parseBase64Binary(encrypted);
decrypted = new String(cipher.doFinal(stringBinary));
} catch (Exception e) {
e.printStackTrace();
}
return decrypted;
}
}
或者我使用 XOR cipher 这样的东西会更好吗?
如果您确实想对用户隐藏这些值,我不明白您为什么要对它们进行加密或散列处理。您应该做的是生成随机且唯一的密钥。您是否考虑过使用 GUID?
如果您认为 GUID 太长,您可以生成一个预定义长度的随机字符串。使您的数据库列成为唯一索引,以便更快地进行搜索。
这就是 URL 缩短器(例如 bit.ly 或 goo.gl 的工作原理。这将防止任何人使用主键在您的数据库中进行爬网。
是的,加密 可能有点矫枉过正。但是,您不应该陷入陷阱并因此而使用不太安全的加密原语。特别是使用 CBC 模式安全性、静态盐等确实是您应该避免的错误。 XOR或DES加密真是名不副实,分分钟能破解这些加密方案
如果加密对服务器性能影响太大,这个问题只有您可以回答。通常 IO 和复杂的 SQL 查询会给您的系统带来比几个字节数据的简单对称加密更多的任务。
我将向您展示使用 GCM 改造的 class 以及 - 如果不可用或者 12 字节的标签大小开销太大 - CTR 模式加密。
只有在无法使用(随机)GUID 时,您才应该这样做。
警告:我认为以下 classes 没有足够的输入参数检查(整数溢出等)。当我尝试实施所有这些检查和 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);
}
}
我需要从最终用户输出中隐藏某些原始值(主要是主键,每个请求可能大约有 100 个),这样数据库就不容易被探测或迭代。加密速度比值的实际不可渗透性更重要。
我目前在以下包装器中使用 PBEWithMD5AndDES
- 这是不是太过分了?我刚开始使用 Java 作为 Web 后端。
public class SecurityHelper {
private static final String DEFAULT_KEY = "some-key-here";
private SecretKey secretKey;
private PBEParameterSpec parameterSpec;
private Cipher cipher;
public SecurityHelper() {
try {
char[] moduleKeyChars = DEFAULT_KEY.toCharArray();
KeySpec keySpec = new PBEKeySpec(moduleKeyChars);
secretKey = SecretKeyFactory.getInstance(
"PBEWithMD5AndDES").generateSecret(keySpec);
parameterSpec = new PBEParameterSpec(new byte[8], 1);
cipher = Cipher.getInstance("PBEWithMD5AndDES");
} catch (Exception e) {
e.printStackTrace();
}
}
public String encrypt(String secret) {
String encrypted = null;
try {
cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec);
byte[] stateBytes = cipher.doFinal(secret.getBytes("UTF-8"));
encrypted = DatatypeConverter.printBase64Binary(stateBytes);
} catch (Exception e) {
e.printStackTrace();
}
return encrypted;
}
public String decrypt(String encrypted) {
String decrypted = null;
try {
cipher.init(Cipher.DECRYPT_MODE, secretKey, parameterSpec);
byte[] stringBinary = DatatypeConverter.parseBase64Binary(encrypted);
decrypted = new String(cipher.doFinal(stringBinary));
} catch (Exception e) {
e.printStackTrace();
}
return decrypted;
}
}
或者我使用 XOR cipher 这样的东西会更好吗?
如果您确实想对用户隐藏这些值,我不明白您为什么要对它们进行加密或散列处理。您应该做的是生成随机且唯一的密钥。您是否考虑过使用 GUID?
如果您认为 GUID 太长,您可以生成一个预定义长度的随机字符串。使您的数据库列成为唯一索引,以便更快地进行搜索。
这就是 URL 缩短器(例如 bit.ly 或 goo.gl 的工作原理。这将防止任何人使用主键在您的数据库中进行爬网。
是的,加密 可能有点矫枉过正。但是,您不应该陷入陷阱并因此而使用不太安全的加密原语。特别是使用 CBC 模式安全性、静态盐等确实是您应该避免的错误。 XOR或DES加密真是名不副实,分分钟能破解这些加密方案
如果加密对服务器性能影响太大,这个问题只有您可以回答。通常 IO 和复杂的 SQL 查询会给您的系统带来比几个字节数据的简单对称加密更多的任务。
我将向您展示使用 GCM 改造的 class 以及 - 如果不可用或者 12 字节的标签大小开销太大 - CTR 模式加密。
只有在无法使用(随机)GUID 时,您才应该这样做。
警告:我认为以下 classes 没有足够的输入参数检查(整数溢出等)。当我尝试实施所有这些检查和 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);
}
}