加密填充AES和RSA

加密填充

只针对 aes 和rsa 加密。rsa签名是另外一个填充方式。其他加密算法使用不多。

  1. 为什么需要填充?
  2. 为什么需要知道填充?

为什么需要填充?

RSA和AES虽然属于两种截然不同的加密类型,但它们都属于块密码的应用范畴。
1.AES的块大小是固定的16字节,RSA的块大小根据密钥长度和填充方式而定。由于AES每次只能处理固定长度的数据(即一个块大小),当数据大小不是块大小的整数倍时,就需要对原始数据进行填充,因此填充对AES来说是技术原理上的需求。因为无法保证数据一定是对齐的。
2.RSA则不太一样,RSA填充的主要目的是为了加强算法的安全性。RSA填充在安全上的作用体现在以下两个方面:一方面,填充后明文长度变长,对应的密文长度也变长了;另一方面,某些填充方式会在明文中加入伪随机信息,将给定的明文消息加密为不同的密文。选择不填充只是会默认补0而已。

为什么需要知道填充?

关于这个,对于AES来说意义不大。对应RSA 来说,这个就决定了明文的长度。
因为AES一般都是多次的,但是RSA不一定希望多次,毕竟计算量太大。
RSA 一次加密的明文长度理论上是:密钥长度 128 bytes/1024 bi(keyLen/8)。所以,会因为填充模式的要求,明文长度出现一定的限制。
另外 ,RSA OEAP的填充模式,在各个语言上实现有点不一样(Android keyStore和Java也不一样)。所以,如果一定要选择oeap填充,除非明确两方实现相同,不然最好自己处理。

填充

AES填充

AES 的CTR和GCM不能填充。首先这两种模式下,明文不参加密钥运算,只是参与最后的异或,所以对明文长度没有要求。

本文只讨论几种比较常记的填充
pkcs5和pkcs7.
这两种对于AES 没有区别,规则就是:
加密:
数据长度%16=n。
n>0
数据 n个n。
n==0;
数据 16个16.
解密:
len=data.length
n= data[len-1]
然后倒序便利n个,必须都是等于n,证明是正常填充,否则就是无填充。但是不绝对,如果原始数据刚好符合,就会造成数据丢失,所以必须协商清楚,不能根据数据处理。

RSA 填充

RSA android里面最大加密长度: keyLen/8,但是极限长度的情况下,数据需要比Modulus要小,不然也是会报错:DATA_TOO_LARGE_FOR_MODULUS。

if (BN_ucmp(f, rsa->n) >= 0) {
    // usually the padding functions would catch this
    OPENSSL_PUT_ERROR(RSA, RSA_R_DATA_TOO_LARGE_FOR_MODULUS);
    goto err;
  }

1,NOPADDING
生成一个长度固定的keyLen/8,从尾部开始往前拷贝原数据进去,其他地方默认就是0.这种模式,计算结果固定,再次因为前面都是0,无法跟原始数据的0区分开,不推荐这种模式。而且这种模式要注意原数据可能会比Modulus大,造成加密失败。

2,RSA_PKCS1_PADDING
RSA_PKCS1_PADDING即PKCS#1 v1.5,属于PKCS#1的一个早期版本。在该填充模式下,如果明文长度不够K字节,加密的时候会在明文中随机填充一些数据,结果是相同的明文每次加密后的密文都不一样。这个规则也是固定填充,数据都是固定的。

a.生成长度为k-mLen-3的字节串PS包含随机生成的非零的伪随机数。PS的长度最小为8个字节。
b.级联PS,消息M和其它的填充组成长度为k的消息EM
EM = 0x00 || 0x02 || PS || 0x00 || M

这种模式下 ,明文最长k-11(比如128长度就是117)。

3.OEAP

PKCS#1 v2.1: RSA密码学规范中关于 OAEP的模式的讲解如下:

RSAES-OAEP-ENCRYPT  (( n,  e),  M, L )
可选:  Hash  哈希函数(hLen代表哈希函数的输出字节数)
MGF  掩码生成函数
输入:
(n,e)  输入的RSA公钥(k代表RSA模数n的字节长度)
M  待加密的数据, 一个长度为mLen的字节串,并且mLen<=k-2hLen-2
L  可选的和消息关联的标签;如果L没有提供,默认的值是空串
输出:
C  加密输出,长度为k的字节串
错误:  “消息太长”;“标签太长”
假设:  RSA公钥(n,e)是有效的。
操作:
1.长度检查
a.如果L的长度超过了哈希函数的输入限制(SHA-1是2^61-1字节),输出"标签太长“并中止。
b.如果mLen>k-2hLen-2,输出“消息太长”并中止。
2.EME-OAEP编码
a.如果标签L没有提供,使得L为空串。使得lHash=Hash(L),一个长度为hLen的字节串。
b.生成一个字节串PS,由k-mLen-2hLen-2个字节零组成,PS的长度有可能是0。
c.连接lHash,PS,一个单字节值为0x01和消息M,形成一个长度为k-hLen-1的字节串: DB = lHash || PS || 0x01 || M
d.生成一个长度为hLen的随机字节串种子seed。
e.使得 dbMask = MGF(seed, k-hLen-1)
f.使得 maskedDB = DB ⊕ dbMask.
g.使得 seedMask = MGF(maskedDB, hLen)
h.使得 maskedSeed = seed ⊕ seedMask
i.级联值为0x00的单字节,maskedSeed,和maskedDB形成长度为k的消息EM: EM = 0x00 || maskedSeed || maskedDB

这种模式下,每次填充都是随机。明文长度限制,与选择的hash算法相关。
关于这种模式需要说明的:L。label部分,算法要求,会参与hash 计算,但是在Android keystore hash的L固定是空串,而且不允许修改。与其他语言协作的时候注意。
另外:mgf里面的hash算法其实可以与外层hash算法不一样,只要两边协商就行。注意这里有个点,如果是go的oeap填充,mgf1里面的hash函数要和外面的hash一致。所以一般都使用一样的hash。
贴一些工具代码:

import java.security.MessageDigest;

/**
 * 掩模生成函数
 * mask generator function, as described in PKCS1v2.
 */
public class MGF1
{
    private MessageDigest digest;

    /**
     * Create a version of MGF1 for the given digest.
     *
     * @param digest
     *            digest to use as the basis of the function.
     */
    public MGF1(MessageDigest digest)
    {
        this.digest = digest;
    }

    /**
     * int to octet string.
     */
    private void I2OSP(int i, byte[] sp)
    {
        sp[0] = (byte) (i >>> 24);
        sp[1] = (byte) (i >>> 16);
        sp[2] = (byte) (i >>> 8);
        sp[3] = (byte) (i >>> 0);
    }

    /**
     * Generate the mask.
     *
     * @param seed
     *            source of input bytes for initial digest state
     * @param length
     *            length of mask to generate
     *
     * @return a byte array containing a MGF1 generated mask
     */
    public byte[]
    generateMask(byte[] seed, int length)
    {
        byte[] mask = new byte[length];
        byte[] C = new byte[4];
        int counter = 0;
        int hLen = digest.getDigestLength();
        digest.reset();
        while (counter < (length / hLen))
        {
            I2OSP(counter, C);
            digest.update(seed);
            digest.update(C);//update 就是把之前的跟现在的连接在一起
            System.arraycopy(digest.digest(), 0, mask, counter * hLen, hLen);

            counter++;
        }
        if ((counter * hLen) < length)
        {
            I2OSP(counter, C);
            digest.update(seed);
            digest.update(C);
            System.arraycopy(digest.digest(), 0, mask, counter * hLen,
                    mask.length - (counter * hLen));
        }
        return mask;
    }
}



import android.support.annotation.IntDef;
import android.support.annotation.StringDef;
import android.text.TextUtils;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.Arrays;

/**
 * @author ricardo
 * @date 2020/10/28.
 * description:
 */
public class OAEPUtils {

    /**
     * @hide
     */
    @IntDef(flag = true, value = {
            BLOCK_SIZE_2048,
            BLOCK_SIZE_4096
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface BlockSize {
    }

    public static final int BLOCK_SIZE_2048 = 2048 / 8;
    public static final int BLOCK_SIZE_4096 = 4096 / 8;

    /**
     * @hide
     */
    @StringDef(value = {
            SHA_1,
            SHA_224,
            SHA_256,
            SHA_384,
            SHA_512
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface HashName {
    }

    public static final String SHA_1 = "SHA-1";
    public static final String SHA_224 = "SHA-224";
    public static final String SHA_256 = "SHA-256";
    public static final String SHA_384 = "SHA-384";
    public static final String SHA_512 = "SHA-512";
  public static byte[] rsaOAEPMGF1SHA1PaddingEMEncode(byte[] sourceData, @HashName String hashName, String label, @BlockSize int blockSize) throws Exception {
        byte[] EM = null;
        if (sourceData == null || sourceData.length == 0) {
            return sourceData;
        }
        if (blockSize != BLOCK_SIZE_2048 || blockSize != BLOCK_SIZE_4096) {
            throw new Exception("block size error");
        }
        byte[] lHash = SHAUtils.encrypt(TextUtils.isEmpty(label) ? "" : label, hashName);
        if (lHash == null) {
            throw new Exception("hash error");
        }

        if (blockSize - 2 * lHash.length - 2 < sourceData.length) {
            throw new Exception(" data block size error");
        }

        byte[] ps = new byte[blockSize
                - sourceData.length - 2 * lHash.length - 2];
        for (int i = 0; i < ps.length; i++) {
            ps[i] = 0;
        }

        byte[] DB = new byte[blockSize
                - lHash.length - 1];
        System.arraycopy(lHash, 0, DB, 0, lHash.length);
        System.arraycopy(ps, 0, DB, lHash.length, ps.length);
        DB[lHash.length + ps.length] = 1;
        System.arraycopy(sourceData, 0, DB, lHash.length + ps.length + 1, sourceData.length);
        byte[] seed = SecureRandom.getSeed(lHash.length);

        MGF1 mgf1 = new MGF1(MessageDigest.getInstance(SHA_1));
        byte[] dbMask = mgf1.generateMask(seed, blockSize - lHash.length - 1);

        // DB和dbMask做异或运算生成maskedDB
        byte[] maskedDB = new BigInteger(DB).xor(new BigInteger(dbMask))
                .toByteArray();

        byte[] seedMask = mgf1.generateMask(maskedDB, lHash.length);

        // seed和seedMask做异或运算生成maskedSeed
        byte[] maskedSeed = new BigInteger(seed).xor(new BigInteger(seedMask))
                .toByteArray();

        EM = new byte[blockSize];
        EM[0] = 0;
        System.arraycopy(maskedSeed, 0, EM, 1, maskedSeed.length);
        System.arraycopy(maskedDB, 0, EM, maskedSeed.length + 1,
                maskedDB.length);
        return EM;
    }

    public static byte[] rsaOAEPMGF1SHA1PaddingEMDecode(byte[] EM, @HashName String hashName, String label, @BlockSize int blockSize) throws Exception {
        byte[] sourceData = null;
        if (EM == null || EM.length == 0 || EM.length != blockSize) {
            return sourceData;
        }
        if (blockSize != BLOCK_SIZE_2048 || blockSize != BLOCK_SIZE_4096) {
            throw new Exception("block size error");
        }
        byte[] lHash = SHAUtils.encrypt(TextUtils.isEmpty(label) ? "" : label, hashName);
        if (lHash == null) {
            throw new Exception("hash error");
        }
        byte[] maskSeed = Arrays.copyOfRange(EM, 1, lHash.length + 1);
        byte[] maskDB = Arrays.copyOfRange(EM, lHash.length + 1, EM.length);
        MGF1 mgf1 = new MGF1(MessageDigest.getInstance(SHA_1));
        byte[] seedMask = mgf1.generateMask(maskDB, lHash.length);
        byte[] seed = new BigInteger(maskSeed).xor(new BigInteger(seedMask)).toByteArray();
        byte[] dbMask = mgf1.generateMask(seed, blockSize - lHash.length - 1);
        byte[] DB = new BigInteger(maskDB).xor(new BigInteger(dbMask)).toByteArray();

        int index = -1;
        for (int k = 0; k < lHash.length; k++) {
            if (lHash[k] != DB[k]) {
                throw new Exception("hash error");
            }
        }
        for (int j = lHash.length; j < DB.length; j++) {
            if (DB[j] == 1) {
                index = j + 1;
                break;
            }
        }
        sourceData = Arrays.copyOfRange(DB, index, DB.length);
        return sourceData;
    }

}

  1. RSA-PSS
    与上面的基本类似,主要是用于签名的。因为会对原文有再hash操作,所以不能用于加密。
    在这里插入图片描述
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值