【问题标题】:Java encryption performance to hide primary keys隐藏主键的 Java 加密性能
【发布时间】:2015-04-18 22:42:50
【问题描述】:

我需要从最终用户输出中隐藏某些原始值(主要是主键,每个请求可能是大约 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 之类的东西会更好吗?

【问题讨论】:

  • 如果您不想显示主键,为什么要首先选择它们以返回?这在我看来是XY problem
  • 构建应用 API。客户端将需要其他方法的 id。示例:/fetch 返回一堆 id,/delete 需要一个 id。
  • 您是否考虑过使用/实施授权机制来防止未经授权访问您的资源?

标签: java security google-app-engine encryption


【解决方案1】:

如果您确实想对用户隐藏这些值,我不明白为什么您应该加密或散列它们。您应该做的是生成随机且唯一的密钥。您是否考虑过改用 GUID?

如果您认为 GUID 太长,您可以生成一个预定义长度的随机字符串。将您的数据库列设为唯一索引,以加快搜索速度。

这就是诸如 bit.ly 或 goo.gl 之类的 URL 缩短器的工作原理。这将防止任何人使用主键爬取您的数据库。

【讨论】:

  • @Cal 如果 性能 是您主要关心的问题,请考虑 this answer,因为 GUID 只生成一次,您可以完全消除任何加密/解密或对每个请求进行散列/查找
  • 这个答案不起作用。他无法弥补 id,因为他需要将它们映射回真实的 id,然后用户将它们传回。
  • GUID 将是“真实 ID”,不涉及映射; OP 声明的目标是防止探测未经授权的资源
【解决方案2】:

是的,加密可能是矫枉过正。但是,您不应因此而陷入陷阱并使用不太安全的加密原语。尤其是使用 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);
    }
}

【讨论】:

    猜你喜欢
    • 2013-11-04
    • 2014-10-02
    • 2017-01-17
    • 1970-01-01
    • 1970-01-01
    • 2015-05-25
    • 2017-03-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多