首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >用Java实现AES加密

用Java实现AES加密
EN

Code Review用户
提问于 2016-07-15 13:45:11
回答 1查看 4.4K关注 0票数 8

我不是安全专家,但是在检查我们旗舰产品的AES实现时,我注意到了一些奇怪的事情,比如输出长度与输入长度有关,并且没有明显使用IV。

代码语言:javascript
复制
@Service
public class EncryptionServiceImpl implements EncryptionService {

    /** The logger for this class */
    private static final Logger LOGGER = new Logger(EncryptionServiceImpl.class);

    /** There's one and only one instance of this class */
    private volatile static EncryptionServiceImpl INSTANCE;

    /** True if EncryptionService is initialized. */
    private boolean isInitialized = false;

    private Cipher cipherEncrypt;
    private Cipher cipherDecrypt;
    private String keyHex;

    /**
     * Constructor is private, use getInstance to get an instance of this class
     */
    private EncryptionServiceImpl() {
        initialize();
    }

    /**
     * Returns the singleton instance of this class.
     * 
     * @return the singleton instance of this class.
     */
    public static EncryptionServiceImpl getInstance() {
        if (INSTANCE == null) {
            synchronized (EncryptionServiceImpl.class) {
                if (INSTANCE == null) {
                    INSTANCE = new EncryptionServiceImpl();
                }
            }
        }
        return INSTANCE;
    }

    /**
     * Initialize EncryptionService.
     */
    private synchronized void initialize() {
        if (!isInitialized){

            // Get key from SystemSettings.
            SystemSettingsService systemSettingsService = (SystemSettingsService) ServiceFactory.getInstance().createService(SystemSettingsService.class);
            keyHex = systemSettingsService.getScmuuid();
            byte[] keyBytes;

            // If keyHex is not blank (field "scmuuid" already exists in the database):
            if (StringUtils.isNotBlank(keyHex)) {
                keyBytes = hexToBytes(keyHex);
                SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, "AES");
                try {
                    cipherEncrypt = Cipher.getInstance("AES");
                    cipherDecrypt = Cipher.getInstance("AES");
                    cipherEncrypt.init(Cipher.ENCRYPT_MODE, secretKeySpec);
                    cipherDecrypt.init(Cipher.DECRYPT_MODE, secretKeySpec);
                } catch (InvalidKeyException e) {
                    throw new InitializationFailureException(
                            "Failure to generate a new encryption key.", e);
                } catch (NoSuchAlgorithmException e) {
                    throw new InitializationFailureException(
                            "Failure to generate a new encryption key.", e);
                } catch (NoSuchPaddingException e) {
                    throw new InitializationFailureException(
                            "Failure to generate a new encryption key.", e);
                }
                //EncryptionService is initialized.
                isInitialized = true;
            } else {
                /*
                 * If keyHex is blank, either we have an SQL exception or the key hasn't
                 * been generated yet. Trying to use the EncryptionService without proper
                 * initialization, will throw a FatalException.
                 * If the key hasn't been generated yet, the next exception will trigger
                 * the caller to use the generateKey() method in the catch block. 
                 */
                //throw new NoEncryptionkeyException();
            }
        }
    }

    /**
     * @see shared.bs.encryption.EncryptionService#isInitialized()
     */
    public boolean isInitialized() {
        return isInitialized;
    }

    /**
     * @see shared.bs.encryption.EncryptionService#decrypt()
     */
    public String decrypt(String value) {
        if (StringUtils.isBlank(value)){
            return null;
        }
        // NULL values from log files can be interpreted as a String with value "null" (see e.g. bug REDACTED)
        if (value != null && value.equalsIgnoreCase("null")) {
            return null;
        }
        if (getCipherDecrypt() == null) {
            throw new EncryptionFailureException("Decryption failure. EncryptionService is not properly initialized.");
        }
        byte[] encryptedBytes = null;
        byte[] decryptedBytes = null;
        try {
            encryptedBytes = hexToBytes(value);
            decryptedBytes = cipherDecrypt.doFinal(encryptedBytes);
        } catch (NumberFormatException e) {
            throw new EncryptionFailureException("Decryption failure.", e);
        } catch (IllegalBlockSizeException e) {
            throw new EncryptionFailureException("Decryption failure.", e);
        } catch (BadPaddingException e) {
            throw new EncryptionFailureException("Decryption failure.", e);
        }
        return new String(decryptedBytes);
    }

    /**
     * @see shared.bs.encryption.EncryptionService#encrypt()
     */
    public String encrypt(String value) {
        if (StringUtils.isBlank(value)){
            return null;
        }
        if (getCipherEncrypt() == null) {
            throw new EncryptionFailureException("Encryption failure. EncryptionService is not properly initialized.");
        }
        byte[] encrypted = null;
        String encHex = null;
        try {
            encrypted = cipherEncrypt.doFinal(value.getBytes());
            encHex = asHex(encrypted);
        } catch (IllegalBlockSizeException e) {
            throw new EncryptionFailureException("Encryption failure.", e);
        } catch (BadPaddingException e) {
            throw new EncryptionFailureException("Encryption failure.", e);
        } catch (NumberFormatException e) {
            throw new EncryptionFailureException("Encryption failure.", e);
        }
        return encHex;
    }

    /** convert a byte array to a hex String */
    private String asHex(byte buf[]) {
        StringBuffer strbuf = new StringBuffer(buf.length * 2);
        int i;
        for (i = 0; i < buf.length; i++) {
            if (((int) buf[i] & 0xff) < 0x10) {
                strbuf.append("0");
            }
            strbuf.append(Long.toString((int) buf[i] & 0xff, 16));
        }
        return strbuf.toString();
    }

    /** convert a hex String to a byte array */
    private byte[] hexToBytes(String hex) {
        byte[] bts = new byte[hex.length() / 2];
        for (int i = 0; i < bts.length; i++) {
            bts[i] = (byte) Integer.parseInt(hex.substring(2 * i, 2 * i + 2), 16);

        }
        return bts;
    }

    /**
     * @see shared.bs.encryption.EncryptionService#generateKey()
     */
    public synchronized void generateKey(){

        if (!isInitialized){

            /*
             * Make sure the key really doesn't already exist and that an initialization failure
             * isn't the result of an earlier SQLException.
             * Try again retrieving the key from the database. 
             */
            SystemSettingsService systemSettingsService = (SystemSettingsService) ServiceFactory.getInstance().createService(SystemSettingsService.class);
            String tryKeyHex = systemSettingsService.getScmuuid();
            if (StringUtils.isNotBlank(tryKeyHex)) {
                // Something came back from the database, we try to initialize again and return silently.
                initialize();
                return;
            }

            // Generate a new 128 bit strong AES key.
            KeyGenerator kgen;
            try {
                kgen = KeyGenerator.getInstance("AES");
            } catch (NoSuchAlgorithmException e) {
                throw new InitializationFailureException(
                        "Failure to generate a new encryption key.", e);
            }
            kgen.init(128); // 128 is in standard JCE
            SecretKey secretKey = kgen.generateKey();
            byte[] keyBytes = secretKey.getEncoded();
            keyHex = asHex(keyBytes);

            // We have a keyHex, it's time to generate the ciphers:
            SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, "AES");
            try {
                cipherEncrypt = Cipher.getInstance("AES");
                cipherDecrypt = Cipher.getInstance("AES");
                cipherEncrypt.init(Cipher.ENCRYPT_MODE, secretKeySpec);
                cipherDecrypt.init(Cipher.DECRYPT_MODE, secretKeySpec);
            } catch (InvalidKeyException e) {
                throw new InitializationFailureException(
                        "Failure to generate a new encryption key.", e);
            } catch (NoSuchAlgorithmException e) {
                throw new InitializationFailureException(
                        "Failure to generate a new encryption key.", e);
            } catch (NoSuchPaddingException e) {
                throw new InitializationFailureException(
                        "Failure to generate a new encryption key.", e);
            }

            /*
             * Persist keyHex (field scmuuid in SystemSettings) and encrypt all existing non-encrypted 
             * passwords and secure build/deploy-parameters in the database with this new key.
             * removed for brevity in this example: each of these is an extra method call.
             */            

        }//End: if (!isInitialized)
    }

我的印象是,这有改进的余地,因为有关明文的信息正在泄露。请注意,由于整个项目的遗留代码(主要是数据库字段长度),我们所有的密文输出必须小于255个字符。实际上,这意味着输出必须是224字节长。

是的,我知道我们在加密密码。这些不是用户密码。这些都是通过诸如和LDAP这样的外部系统来处理的。这些加密密码用于对外部第三方系统进行身份验证,在这些系统中,实现基于令牌的身份验证方案要么是不可能的,要么是不可行的,或者是尝试失败的。

EN

回答 1

Code Review用户

发布于 2016-07-15 14:02:07

代码语言:javascript
复制
/**
 * @see shared.bs.encryption.EncryptionService#decrypt()
 */
public String decrypt(String value) {
    if (StringUtils.isBlank(value)){
        return null;
    }
    // NULL values from log files can be interpreted as a String with value "null" (see e.g. bug REDACTED)
    if (value != null && value.equalsIgnoreCase("null")) {
        return null;
    }

来自StringUtils.isBlankStringUtils.isBlank的文档:

返回:如果字符串为空、空或空白,则为 true

因此,这里不需要对value进行空检查,可以进行检查。

代码语言:javascript
复制
} catch (InvalidKeyException e) {
    throw new InitializationFailureException(
            "Failure to generate a new encryption key.", e);
} catch (NoSuchAlgorithmException e) {
    throw new InitializationFailureException(
            "Failure to generate a new encryption key.", e);
} catch (NoSuchPaddingException e) {
    throw new InitializationFailureException(
            "Failure to generate a new encryption key.", e);
}

对于这类事情,你有多个收获。

代码语言:javascript
复制
} catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException e) {
    throw new InitializationFailureException(
            "Failure to generate a new encryption key.", e);
}

多捕获保持您的代码紧凑,并避免您不得不复制粘贴异常消息。

票数 4
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/134980

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档