浅谈数据加密策略


浅谈之前,我们不妨先了解几个知识点:加密技术、哈希摘要。

加密技术 分为 对称加密非对称加密 两类。
对称加密即加密密钥与解密密钥相同,形象的描述为,你有你家房门的钥匙,你既可以用它锁住房门,也可以用它打开房门。常见的算法有DES、AES。
而非对称加密则采用两把钥匙,公钥和私钥。使用公钥加密的信息只能通过私钥解密,反之,使用私钥加密的信息也只能通过公钥解密。这分别对应两个过程:数据加密传输与数字签名。本文仅谈论数据的加密传输。常见的算法有RSA。

哈希摘要 即指通过散列函数为一段数据生成固定长度的摘要,而不论数据的大小。生成的摘要一般难以推导出原数据。常见的散列函数有MD5、SHA-1、SHA-2、SHA-3(Keccak)。


一、数据加密传输策略的设计

从加密技术中,我们可以得到两种简单的加密传输的方式:使用对称加密或使用非对称加密。其各有优劣。

对称加密非对称加密
加密效率计算量小,加密效率高计算复杂,加密效率低
密钥传输不宜明文传输,以免泄漏密钥造成数据泄漏可传输公钥,使用私钥解密以获得数据

因此,结合使用对称加密与非对称加密,接收方先用发送方的非对称加密的公钥加密自己的对称加密的密钥,发送方再用其私钥解密出对称加密的密钥,双方通过对称加密的密钥传输数据,才是更好的选择。这里我们以客户端(数据接收方)和服务端(数据发送方)为例:

  1. 服务端使用非对称加密生成公钥(publicKey)和私钥(privateKey),客户端使用对称加密生成密钥(key)
  2. 客户端获取publicKey,使用其加密key,得到key的密文(keyCiphertext)
  3. 服务端使用privateKey解密keyCiphertext得到key
  4. 服务端使用key加密数据(data)得到数据的密文(dataCiphertext)
  5. 客户端获取dataCiphertext,使用key解密得到data

至此,就完成了数据发送方(服务端)向数据接收方(客户端)的加密数据传输

二、密码的加密存储策略

不论是应用程序亦或是网页,都可能涉及到用户的登录,其背后也必然牵扯到数据库对用户密码的存储与验证。直接存储密码确实简单易行,但若发生数据泄漏则将可能产生严重后果,因此用户密码的存储也应当加密。
结合哈希摘要,我们不难想到一种方法:

  1. 将用户密码通过哈希函数加密生成摘要,并存储
  2. 用户登录时,对输入的密码使用相同的哈希函数加密生成摘要
  3. 对比两次摘要,即可验证密码

但实际上,若用户密码设计的简单(如个人生日等),很容易通过反查询(即先对多个数据一 一对应的生成摘要,再依次对比摘要以确定原数据)的方式破解用户密码。因此,加盐值(salt值)的概念应运而生。所谓加盐值,指的是一串随机生成的数据,将其以任意方式添加到原来的数据中,以提升数据复杂度的一种方法,就像是往一杯干净的水加粗盐而变得浑浊一样。因此,新的密码加密存储方式我们可以这样设计:

  1. 随机生成一串字符串(即盐值),将其以任意方式添加到用户密码中,生成摘要,同时存储摘要和盐值
  2. 用户登录时,取出盐值,将其以相同的方式添加到输入的密码中,生成摘要
  3. 对比两次摘要,即可验证密码

三、示例代码

代码中所采用的对称加密算法为AES,采用的非对称加密算法为RSA。数据的发送方为服务器端Java程序,数据的接收方为Android应用,所采用的字节数据编码方式为Base64。

数据发送方

    //-----------------AES------------------
    private byte[] aesKeyBytes = null;// AES密钥字节数组形式
    private Key aesKey = null;// AES密钥

    /**
     * 获得使用Base64编码的未加密AES密钥字符串.
     *
     * @return AES密钥字符串形式
     */
    public String getAESKeyNoEncrypt() {
        return new String(Base64.getEncoder().encode(aesKeyBytes), StandardCharsets.UTF_8);
    }

    /**
     * 使用AES密钥加密数据.
     *
     * @param data 待加密的数据
     * @return 加密后的密文,使用Base64编码
     */
    public String aesEncryptData(String data) {
        try {
            if (aesKey == null) {
                throw new NullPointerException("AES密钥为空");
            }
            if (data == null) {
                throw new NullPointerException("待加密数据为空");
            }
            Cipher cipher = Cipher.getInstance("AES");
            cipher.init(Cipher.ENCRYPT_MODE, aesKey);
            return new String(Base64.getEncoder().encode(cipher.doFinal(data.getBytes(StandardCharsets.UTF_8))), StandardCharsets.UTF_8);
        } catch (Exception e) {
            myLog.e("AES加密数据失败", e);
            return null;
        }
    }

    /**
     * 使用AES密钥解密数据.
     *
     * @param ciphertext 待解密的使用Base64编码的密文
     * @return 解密后的数据
     */
    public String aesDecryptData(String ciphertext) {
        try {
            if (aesKey == null) {
                throw new NullPointerException("AES密钥为空");
            }
            if (ciphertext == null) {
                throw new NullPointerException("待解密密文为空");
            }
            byte[] decodeCiphertextBytes = Base64.getDecoder().decode(ciphertext.getBytes(StandardCharsets.UTF_8));
            Cipher cipher = Cipher.getInstance("AES");
            cipher.init(Cipher.DECRYPT_MODE, aesKey);
            return new String(cipher.doFinal(decodeCiphertextBytes), StandardCharsets.UTF_8);
        } catch (Exception e) {
            myLog.e("AES解密数据失败", e);
            return null;
        }
    }

    //-----------------RSA------------------
    // RSA允许你选择公钥的大小。512位的密钥被视为不安全的;768位的密钥
    // 不用担心受到除了国家安全管理(NSA)外的其他事物的危害;1024位的
    // 密钥几乎是安全的。
    //                                 ————来源于百度百科(RSA算法)
    private static final int rsaKeySize = 1024;// RSA密钥长度
    private static PrivateKey privateKey = null;// RSA私钥
    private static String publicKeyStr = null;// RSA公钥字符串形式

    /**
     * 生成RSA密钥对.
     */
    public static void generateRSAKeyPair() {
        try {
            KeyPairGenerator rsa = KeyPairGenerator.getInstance("RSA");
            rsa.initialize(rsaKeySize);
            KeyPair keyPair = rsa.generateKeyPair();
            privateKey = keyPair.getPrivate();
            byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
            publicKeyStr = new String(Base64.getEncoder().encode(publicKeyBytes), StandardCharsets.UTF_8);
        } catch (Exception e) {
            myLog.e("生成RSA密钥对失败", e);
        }
    }

    /**
     * 获得RSA公钥的字符串形式,使用Base64编码.
     *
     * @return RSA公钥字符串
     */
    public static String getRSAPublicKeyStr() {
        return publicKeyStr;
    }

    /**
     * RSA私钥解密含密钥字符串以构造AES密钥.
     *
     * @param aesKeyStr 以Base64编码的RSA公钥加密后的AES密钥字符串
     */
    private void decryptAESKey(String aesKeyStr) {
        try {
            byte[] decodeAESKeyBytes = Base64.getDecoder().decode(aesKeyStr);
            int maxLength = rsaKeySize / 8;
            int mod = decodeAESKeyBytes.length % maxLength;
            int groupNum = decodeAESKeyBytes.length / maxLength;
            if (mod != 0) {
                groupNum++;
            }
            byte[][] dataSrc = new byte[groupNum][0];
            for (int i = 0, start = 0; i < groupNum; i++, start += maxLength) {
                if (i != groupNum - 1 || mod == 0) {
                    dataSrc[i] = Arrays.copyOfRange(decodeAESKeyBytes, start, start + maxLength);
                } else {
                    dataSrc[i] = Arrays.copyOfRange(decodeAESKeyBytes, start, start + mod);
                }
            }
            Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            byte[][] cache = new byte[dataSrc.length][0];
            aesKeyBytes = new byte[0];
            for (int i = 0, start = 0; i < dataSrc.length; i++) {
                cache[i] = cipher.doFinal(dataSrc[i]);
                aesKeyBytes = Arrays.copyOf(aesKeyBytes, aesKeyBytes.length + cache[i].length);
                System.arraycopy(cache[i], 0, aesKeyBytes, start, cache[i].length);
                start = cache[i].length;
            }
            aesKey = new SecretKeySpec(aesKeyBytes, "AES");
        } catch (Exception e) {
            myLog.e("解密数据获取AES密钥失败", e);
        }
    }

数据接收方

    //******************加密传输*******************
    private static String encryptAESKey = null;// 使用服务器端RSA公钥加密的AES密钥
    //====================AES=====================
    private static Key aesKey = null;// AES密钥
    private static byte[] aesKeyBytes = null;// AES密钥字节数组

    /**
     * 生成AES密钥.
     */
    public static void generateAESKey() {
        try {
            KeyGenerator aes = KeyGenerator.getInstance("AES");
            aes.init(256);
            aesKeyBytes = aes.generateKey().getEncoded();
            aesKey = new SecretKeySpec(aesKeyBytes, "AES");
        } catch (Exception e) {
            myLog.e("生成AES密钥失败", e);
        }
    }

    /**
     * 使用AES密钥加密数据.
     *
     * @param data 待加密的数据
     * @return 加密后的密文,使用Base64编码
     */
    @SuppressLint("GetInstance")
    private static String aesEncryptData(String data) {
        try {
            if (aesKey == null) {
                throw new NullPointerException("AES密钥为空");
            }
            Cipher cipher = Cipher.getInstance("AES");
            cipher.init(Cipher.ENCRYPT_MODE, aesKey);
            return new String(Base64.encode(cipher.doFinal(data.getBytes(StandardCharsets.UTF_8)), Base64.NO_WRAP), StandardCharsets.UTF_8);
        } catch (Exception e) {
            myLog.e( "AES加密数据失败", e);
            return null;
        }
    }

    /**
     * 使用AES密钥解密数据.
     *
     * @param ciphertext 待解密的使用Base64编码的密文
     * @return 解密后的数据
     */
    @SuppressLint("GetInstance")
    public static String aesDecryptData(String ciphertext) {
        try {
            if (aesKey == null) {
                throw new NullPointerException("AES密钥为空");
            }
            if (ciphertext == null) {
                throw new NullPointerException("密文为空");
            }
            byte[] decodeCiphertextBytes = Base64.decode(ciphertext.getBytes(StandardCharsets.UTF_8), Base64.NO_WRAP);
            Cipher cipher = Cipher.getInstance("AES");
            cipher.init(Cipher.DECRYPT_MODE, aesKey);
            return new String(cipher.doFinal(decodeCiphertextBytes), StandardCharsets.UTF_8);
        } catch (Exception e) {
            myLog.e("AES解密数据失败", e);
            return null;
        }
    }

    //====================RSA=====================
    private static PublicKey publicKey = null;// RSA公钥

    /**
     * 解码RSA公钥字符串以构造RSA公钥.
     */
    private static void revertRSAPublicKey(String rsaPublicKeyStr) {
        try {
            byte[] rsaPublicKeyBytes = Base64.decode(rsaPublicKeyStr.getBytes(StandardCharsets.UTF_8), Base64.NO_WRAP);
            publicKey = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(rsaPublicKeyBytes));
            encryptAESKey = getEncryptAESKey();
        } catch (Exception e) {
            myLog.e("还原RSA公钥失败", e);
        }
    }

    /**
     * 使用RSA公钥加密AES密钥.
     *
     * @return 以Base64编码的加密后的AES密钥字符串
     */
    private static String getEncryptAESKey() {
        try {
            if (publicKey == null) {
                throw new NullPointerException("RSA公钥为空");
            }
            if (aesKeyBytes == null) {
                throw new NullPointerException("AES密钥为空");
            }
            int rsaKeySize = 1024;// RSA密钥长度
            int maxLength = rsaKeySize / 8 - 11;
            int mod = aesKeyBytes.length % maxLength;
            int groupNum = aesKeyBytes.length / maxLength;
            if (mod != 0) {
                groupNum++;
            }
            byte[][] dataSrc = new byte[groupNum][0];
            for (int i = 0, start = 0; i < groupNum; i++, start += maxLength) {
                if (i != groupNum - 1 || mod == 0) {
                    dataSrc[i] = Arrays.copyOfRange(aesKeyBytes, start, start + maxLength);
                } else {
                    dataSrc[i] = Arrays.copyOfRange(aesKeyBytes, start, start + mod);
                }
            }
            byte[][] cache = new byte[dataSrc.length][0];
            byte[] result = new byte[0];
            Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            for (int i = 0, start = 0; i < dataSrc.length; i++) {
                cache[i] = cipher.doFinal(dataSrc[i]);
                result = Arrays.copyOf(result, result.length + cache[i].length);
                System.arraycopy(cache[i], 0, result, start, cache[i].length);
                start = cache[i].length;
            }
            return new String(Base64.encode(result, Base64.NO_WRAP), StandardCharsets.UTF_8);
        } catch (Exception e) {
            myLog.e("使用RSA公钥加密AES密钥失败", e);
            return null;
        }
    }

能力所限,不足之处烦请多多指教!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值