AES与RSA加密的个人理解

一、AES 加密

对称加密方式,加解密用同一秘钥,速度快,效率高,但是存在密钥交换问题。

密钥交换问题:通过RSA+AES混合加密可以使数据传输更安全些:
移动端生成AES密钥,将要传输的数据通过AES加密后上传到服务端,同时将该AES密钥用RSA公钥加密并上传到服务端;在服务端获得通过RSA公钥加密后的AES密钥和通过AES密钥加密后的数据,用RSA私钥解密得到正确的AES密钥,使用AES密钥对数据源解密,获取明文数据。

1. AES密钥
  • 支持三种密钥长度:128位、192位、256位,大家经常说的AES128、AES256就是指不同的密钥长度。
  • 不同的密钥长度意味着AES加密的轮数不同,1287位加密10轮,192位加密12轮,256位加密14轮,从安全性角度来讲,256位安全性最高,但是128位因为加密轮数少,所以性能更好一些。

这里的长度位是bit,char类型是2字节,一个字节是8位,所以密钥字符串长度为16,就是256位,安全性最高。

2. 填充

AES不是将拿到的明文一次性加密,而是分组加密,就是先将明文切分成长度相等的块,每块大小128bit,再对每一小块进行加密。那么问题就来了,并不是所有的原始明文串能被等分成128bit,例如原串大小200bit,那么第二个块只有72bit,所以就需要对第二个块进行填充处理,让第二个块的大小达到128bit。常见的填充模式有以下三种:

2.1 NoPadding

不进行填充,要求原始加密串大小必须是 128bit 的整数倍

2.2 PKCS5Padding

假设块大小8字节,如果这个块跟8字节还差n个字节,那么就在原始块填充n,直到满8字节。例:块{1,2,3},跟8字节差了5个字节,那么补全后的结果{1,2,3,5,5,5,5,5}后面是五个5,块{1,2,3,4,5,6,7}跟8字节差了1个字节,那么补全后就是{1,2,3,4,5,6,7,1},就是补了一个1。

如果恰好8字节又选择了PKCS5Padding填充方式呢?块{1,2,3…8}填充后变成{1,2,3…8,8…8},原串后面被补了8个8,这样做的原因是方便解密,只需要看最后一位就能算出原块的大小是多少。

2.3 PKCS7Padding

跟PKCS5Padding的填充方式一样,不同的是,PKCS5Padding只是对8字节的进行填充,PKCS7Padding可以对1~256字节大小的block进行填充。

3. 模式

AES有多种加密模式,包括:ECB,CBC,CTR,OCF,CFB,最常见的还是 ECB 和 CBC 模式。

3.1 ECB模式

最简单的一种加密模式,每个块进行独立加密,块与块之间加密互不影响,这样就能并行,效率高。
虽然这样加密很简单,但是不安全,如果两个块的明文一模一样,那么加密出来的东西也一模一样。

3.2 CBC模式

CBC模式中引入了一个新的概念,初始向量iv。iv的作用就是为了防止同样的明文块被加密成同样的内容。原理是第一个明文块跟初始向量做异或后加密,第二个块跟第一个密文块做异或再加密,依次类推,避免了同样的块被加密成同样的内容。

所以跟第三方对接的时候,如果对面说他们用aes加密,务必对他们发起灵魂三问:

  1. AES用的什么模式?
  2. AES的填充方式是什么?有的第三方不用标准的填充方式,令人费解,所以一定要问清楚
  3. 设置的初始向量是什么?(如果对方说他们用cbc)

工具类如下:

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.Base64;

public class AESUtil {
    /**
     * 算法名称
     **/
    public static final String KEY_ALGORITHM = "AES";
    /**
     * 算法名称/加密模式/填充方式
     **/
    public static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding";
    /**
     * 编码
     **/
    public static final String CHARSET_NAME = "UTF-8";

    public static void main(String[] args) throws Exception {
        String source = "15500000000";
        System.out.println("原文: " + source);
        String key = "kT4kMc51VcD/DmKw";
        String encryptData = encrypt(source, key);
        System.out.println("加密后: " + encryptData);
        String decryptData = decrypt(encryptData, key);
        System.out.println("解密后: " + decryptData);
    }
    
    /**
     * 加密数据
     *
     * @param data 待加密数据
     * @param key  密钥
     * @return 加密后的数据
     */
    public static String encrypt(String data, String key) throws Exception {
    	// 生成密钥key对象
        Key secretKey = new SecretKeySpec(key.getBytes(CHARSET_NAME), KEY_ALGORITHM);
        IvParameterSpec iv = new IvParameterSpec(key.getBytes(CHARSET_NAME));
        // 实例化Cipher对象,它用于完成实际的加密操作
        Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
        // 初始化Cipher对象,设置为加密模式
        cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv);
        byte[] results = cipher.doFinal(data.getBytes(CHARSET_NAME));
        // 执行加密操作。加密后的结果通常都会用Base64编码进行传输
        return Base64.getEncoder().encodeToString(results);
    }

    /**
     * 解密数据
     * 
     * @param data 待解密数据
     * @param key  密钥
     * @return 解密后的数据
     */
    public static String decrypt(String data, String key) throws Exception {
        Key secretKey = new SecretKeySpec(key.getBytes(CHARSET_NAME), KEY_ALGORITHM);
        IvParameterSpec iv = new IvParameterSpec(key.getBytes(CHARSET_NAME));
        Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, secretKey, iv);
        return new String(cipher.doFinal(Base64.getDecoder().decode(data)));
    }
}

二、RSA 加密

非对称加密方式。需要两个密钥,一个公钥一个私钥(公钥置于移动端,私钥置于服务端),使用其中一把密钥加密后的明文,只有对应的密钥才能解的开。
rsa可以用来做加密或者做签名,两者性质不一样。

1. 签名和加密的区别

签名的作用是让接受方验证你传过去的数据没有被篡改;加密的作用是保证数据不被窃取。

2. 加密

当移动端向服务端传输重要数据的时候,可以用公钥对数据进行加密再传输;到服务端,服务端使用私钥对该加密过的数据进行解密,获得对应数据。反之,服务端通过私钥对要传输的数据加密,移动端获得加密后的数据通过公钥解密获得对应数据。

工具类如下:

import org.apache.commons.codec.binary.Base64;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

public class RSAEncryptUtil {

    /**
     * 生成公钥、私钥对(keysize=1024)
     *
     * @return
     */
    public KeyPairInfo getKeyPair() {
        return getKeyPair(1024);
    }

    /**
     * 生成公钥、私钥对
     *
     * @param keySize
     * @return
     */
    public KeyPairInfo getKeyPair(int keySize) {
        try {
            KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
            // 初始化密钥对生成器,密钥大小一般要大于1024位,
            keyPairGen.initialize(keySize);
            // 生成一个密钥对,保存在keyPair中
            KeyPair keyPair = keyPairGen.generateKeyPair();
            // 得到私钥
            RSAPrivateKey oraprivateKey = (RSAPrivateKey) keyPair.getPrivate();
            // 得到公钥
            RSAPublicKey orapublicKey = (RSAPublicKey) keyPair.getPublic();

            KeyPairInfo pairInfo = new KeyPairInfo(keySize);
            //公钥
            byte[] publicKeybyte = orapublicKey.getEncoded();
            String publicKeyString = Base64.encodeBase64String(publicKeybyte);
            pairInfo.setPublicKey(publicKeyString);
            //私钥
            byte[] privateKeybyte = oraprivateKey.getEncoded();
            String privateKeyString = Base64.encodeBase64String(privateKeybyte);
            pairInfo.setPrivateKey(privateKeyString);

            return pairInfo;
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 获取公钥对象
     *
     * @param publicKeyBase64
     * @return
     * @throws InvalidKeySpecException
     * @throws NoSuchAlgorithmException
     */
    public PublicKey getPublicKey(String publicKeyBase64)
            throws InvalidKeySpecException, NoSuchAlgorithmException {

        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        X509EncodedKeySpec publicpkcs8KeySpec =
                new X509EncodedKeySpec(Base64.decodeBase64(publicKeyBase64));
        PublicKey publicKey = keyFactory.generatePublic(publicpkcs8KeySpec);
        return publicKey;
    }

    /**
     * 获取私钥对象
     *
     * @param privateKeyBase64
     * @return
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     */
    public PrivateKey getPrivateKey(String privateKeyBase64)
            throws NoSuchAlgorithmException, InvalidKeySpecException {
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PKCS8EncodedKeySpec privatekcs8KeySpec =
                new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyBase64));
        PrivateKey privateKey = keyFactory.generatePrivate(privatekcs8KeySpec);
        return privateKey;
    }

    /**
     * 使用公钥加密
     *
     * @param content         待加密内容
     * @param publicKeyBase64 公钥 base64 编码
     * @return 经过 base64 编码后的字符串
     */
    public String encipher(String content, String publicKeyBase64) {
        return encipher(content, publicKeyBase64, -1);
    }

    /**
     * 使用公钥加密(分段加密)
     *
     * @param content         待加密内容
     * @param publicKeyBase64 公钥 base64 编码
     * @param segmentSize     分段大小,一般小于 keySize/8(段小于等于0时,将不使用分段加密)
     * @return 经过 base64 编码后的字符串
     */
    public String encipher(String content, String publicKeyBase64, int segmentSize) {
        try {
            PublicKey publicKey = getPublicKey(publicKeyBase64);
            return encipher(content, publicKey, segmentSize);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 分段加密
     *
     * @param ciphertext  密文
     * @param key         加密秘钥
     * @param segmentSize 分段大小,<=0 不分段
     * @return
     */
    public String encipher(String ciphertext, Key key, int segmentSize) {
        try {
            // 用公钥加密
            byte[] srcBytes = ciphertext.getBytes();

            // Cipher负责完成加密或解密工作,基于RSA
            Cipher cipher = Cipher.getInstance("RSA");
            // 根据公钥,对Cipher对象进行初始化
            cipher.init(Cipher.ENCRYPT_MODE, key);
            byte[] resultBytes = null;

            if (segmentSize > 0)
                resultBytes = cipherDoFinal(cipher, srcBytes, segmentSize); //分段加密
            else
                resultBytes = cipher.doFinal(srcBytes);

            String base64Str = Base64.encodeBase64String(resultBytes);
            return base64Str;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 分段大小
     *
     * @param cipher
     * @param srcBytes
     * @param segmentSize
     * @return
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     * @throws IOException
     */
    private byte[] cipherDoFinal(Cipher cipher, byte[] srcBytes, int segmentSize)
            throws IllegalBlockSizeException, BadPaddingException, IOException {
        if (segmentSize <= 0)
            throw new RuntimeException("分段大小必须大于0");
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int inputLen = srcBytes.length;
        int offSet = 0;
        byte[] cache;
        int i = 0;
        // 对数据分段解密
        while (inputLen - offSet > 0) {
            if (inputLen - offSet > segmentSize) {
                cache = cipher.doFinal(srcBytes, offSet, segmentSize);
            } else {
                cache = cipher.doFinal(srcBytes, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * segmentSize;
        }
        byte[] data = out.toByteArray();
        out.close();
        return data;
    }

    /**
     * 使用私钥解密
     *
     * @param contentBase64    待加密内容,base64 编码
     * @param privateKeyBase64 私钥 base64 编码
     * @return
     * @segmentSize 分段大小
     */
    public String decipher(String contentBase64, String privateKeyBase64) {
        return decipher(contentBase64, privateKeyBase64, -1);
    }

    /**
     * 使用私钥解密(分段解密)
     *
     * @param contentBase64    待加密内容,base64 编码
     * @param privateKeyBase64 私钥 base64 编码
     * @return
     * @segmentSize 分段大小
     */
    public String decipher(String contentBase64, String privateKeyBase64, int segmentSize) {
        try {
            PrivateKey privateKey = getPrivateKey(privateKeyBase64);
            return decipher(contentBase64, privateKey, segmentSize);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 分段解密
     *
     * @param contentBase64 密文
     * @param key           解密秘钥
     * @param segmentSize   分段大小(小于等于0不分段)
     * @return
     */
    public String decipher(String contentBase64, Key key, int segmentSize) {
        try {
            // 用私钥解密
            byte[] srcBytes = Base64.decodeBase64(contentBase64);
            // Cipher负责完成加密或解密工作,基于RSA
            Cipher deCipher = Cipher.getInstance("RSA");
            // 根据公钥,对Cipher对象进行初始化
            deCipher.init(Cipher.DECRYPT_MODE, key);
            byte[] decBytes = null;//deCipher.doFinal(srcBytes);
            if (segmentSize > 0)
                decBytes = cipherDoFinal(deCipher, srcBytes, segmentSize); //分段加密
            else
                decBytes = deCipher.doFinal(srcBytes);

            String decrytStr = new String(decBytes);
            return decrytStr;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 秘钥对
     */
    public class KeyPairInfo {
        public KeyPairInfo(int keySize) {
            setKeySize(keySize);
        }

        public KeyPairInfo(String publicKey, String privateKey) {
            setPrivateKey(privateKey);
            setPublicKey(publicKey);
        }

        String privateKey;
        String publicKey;
        int keySize = 0;

        public String getPrivateKey() {
            return privateKey;
        }

        public void setPrivateKey(String privateKey) {
            this.privateKey = privateKey;
        }

        public String getPublicKey() {
            return publicKey;
        }

        public void setPublicKey(String publicKey) {
            this.publicKey = publicKey;
        }

        public int getKeySize() {
            return keySize;
        }

        public void setKeySize(int keySize) {
            this.keySize = keySize;
        }
    }

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值