配置文件加解密工具

2021-02-08  本文已影响0人  舌尖上的大胖

项目要求服务端的配置文件需要加密存放,写个工具处理一下。

一、目标

二、思路

三、实现

1、生成密钥对

# 生成密钥,OpenSSL 采用的默认私钥格式为 PKCS#1
$ openssl genrsa -out prv_pkcs1.key 2048

# 私钥用于后面 Java 程序解密,为了不引入三方库,转换为 PKCS#8 的密钥格式
# -nocrypt 用于指定密钥不加密,这里只是演示,实际使用过程中去掉这个参数,根据提示设置密码
# 假设密码为 123456
$ openssl pkcs8 -topk8 -inform PEM -in prv_pkcs1.key -outform pem -nocrypt -out prv_pkcs8.key

# 生成公钥
$ openssl rsa -in prv_pkcs1.key -pubout -out pub.key

2、串联解密、编辑和加密保存

#!/bin/bash

# 遇到错误退出,使用未定义变量时退出
set -eu

PRIVATE_KEY_FILE=prv_pkcs8.key
PUBLIC_KEY_FILE=pub.key

# 读取要编辑的文件
config_file=${1-""}

if [[ -z "${config_file}" ]]; then
    echo '请指定要编辑的文件名'
    exit -1
fi

# 生成临时文件用于存放明文文件
tmp_file=$(mktemp)

# 如果配置文件存在
if [[ -e "${config_file}" ]]; then
    # 解密到临时文件
    openssl rsautl -decrypt -inkey "${PRIVATE_KEY_FILE}" -in "${config_file}" -out "${tmp_file}"
fi

# 获取临时文件编辑前的 HASH
tmp_file_hash_old=$(md5 -q "${tmp_file}")

# 明文编辑明文临时文件
vim "${tmp_file}"

# 获取临时文件编辑后的 HASH
tmp_file_hash_new=$(md5 -q "${tmp_file}")

# 临时文件编辑前后哈希值不同,说明修改了内容
if [[ "${tmp_file_hash_new}" != "${tmp_file_hash_old}" ]]; then
    # 如果配置文件存在
    if [[ -e "${config_file}" ]]; then
        # 以当前时间作为时间戳
        timestamp_cmd='date +%Y%m%d%H%M%S'
        timestamp=$($timestamp_cmd)

        # 备份原配置文件
        cp "${config_file}" "${config_file}_${timestamp}"
    fi
    # 编辑后加密存储
    openssl rsautl -encrypt -inkey "${PUBLIC_KEY_FILE}" -pubin -in "${tmp_file}" -out "${config_file}"
fi

# 删除临时文件
rm "${tmp_file}"

3、Java 解密

public class Sample {
    // 算法
    private static final String RSA_ALGORITHM = "RSA";
    // 私钥密码
    private static final String RSA_PRIVATE_KEY_PASSWORD = "123456";
  
    /**
     * 读取 RSA 私钥
     * @param privateKeyStr PKCS#8 PEM 格式的私钥字符串,不包括首位标记符
     * @return RSA 私钥对象
     */
    public static RSAPrivateKey loadEncryptedPrivateKey(String privateKeyStr) throws IOException, InvalidKeyException, InvalidKeySpecException, NoSuchAlgorithmException {
        // 将 PEM 格式私钥数据转为 DER 格式
        byte[] buffer = Base64.getMimeDecoder().decode(privateKeyStr);
        // 获取加密的私钥信息
        EncryptedPrivateKeyInfo pkInfo = new EncryptedPrivateKeyInfo(buffer);
        // 密码
        PBEKeySpec keySpec = new PBEKeySpec(RSA_PRIVATE_KEY_PASSWORD.toCharArray());
        // 根据密钥信息获取密钥工厂
        SecretKeyFactory pbeKeyFactory = SecretKeyFactory.getInstance(pkInfo.getAlgName());
        // PKCS#8 格式的密钥
        PKCS8EncodedKeySpec encodedKeySpec = pkInfo.getKeySpec(pbeKeyFactory.generateSecret(keySpec));
        KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
        // 真正的密钥
        PrivateKey encryptedPrivateKey = keyFactory.generatePrivate(encodedKeySpec);
        return (RSAPrivateKey) encryptedPrivateKey;
    }

    /**
     * 使用私钥解密数据
     * @param privateKey 私钥
     * @param encrypted  密文数据
     * @return 解密后的明文数据
     */
    public static byte[] decrypt(PrivateKey privateKey, byte[] encrypted) throws InvalidKeyException, BadPaddingException, IllegalBlockSizeException, NoSuchPaddingException, NoSuchAlgorithmException {
        Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        return cipher.doFinal(encrypted);
    }
}

4、补充说明

网上有很多对于 RSA 的处理代码都不能用,总结下来有如下几方面问题:

本文中的 Shell 脚本在 macOS 10.15.7 和 CentOS 7.6 中测试通过,Java 代码在 OpenJDK 11 上测试通过。

5、使用说明

# 假设脚本命名为 confeditor.sh
# 增加可执行权限
$ chmod +x confeditor.sh
# 执行脚本
$ ./confeditor.sh 文件名

6、编写 vim 插件

如果习惯使用 vim 作为编辑器,可以通过编写插件,为特定类型文件在打开和保存事件做自定义处理。这样可以实现在打开文件时,提示输入密钥密码,保存时,自动把明文加密成密文。基本实现用户无感知。

代码不多,为了方便可以直接保存到 ~/.vimrc 中。vim 的插件可以放到 runtime 目录中,
如:~/.vim/
也可以根据作用,放到不同目录中,
如:~/.vim/plugin/encrypt_config/encrypt_config.vim

" 加载文件
function LoadFile(filename)
    " 如果文件存在
    if filereadable(a:filename)
        " 解密文件,把解密后的内容写入缓冲区
        silent! execute '%!openssl rsautl -decrypt -inkey prv_pkcs8.key -in %'
    endif
endfunction

" 打开 *.encryptcfg 文件时,执行自定义加载函数
:autocmd BufReadCmd *.encryptcfg :call LoadFile(bufname('%'))

" 保存文件
function SaveFile(outfile)
    " 获取临时文件路径
    let tmpfile = tempname()
    " 当前缓冲区内容写入临时文件
    silent! execute 'write' fnameescape(tmpfile)
    " 缓冲区状态指定为未修改
    let &modified = 0
    " 加密文件,删除临时文件
    silent! execute '!openssl rsautl -encrypt -inkey pub.key -pubin -in' tmpfile '-out' a:outfile '; rm ' tmpfile
endfunction

" 保存 *.encryptcfg 文件时,执行自定义加密函数
:autocmd BufWriteCmd *.encryptcfg :call SaveFile(bufname('%'))

脚本会自动解密、编辑、加密、保存,并备份原配置文件。

Java 读取加密文件的代码,根据注释调用即可。

四、参考资料

(完)

上一篇 下一篇

猜你喜欢

热点阅读