OAEP及其在OpenSSL中的实现

      • RSA
      • OAEP最优非对称加密填充
        • 1输入
        • 2加密过程
        • 3校验过程
      • OpenSSL对OAEP的实现
        • RSA_padding_add_PKCS1_OAEP_mgf1
        • RSA_padding_check_PKCS1_OAEP_mgf1
        • PKCS1_MGF1
      • 参考文献

1.RSA

RSA是一种经典的公钥密码体制,可以用来做加密或者签名。设RSA的公私钥对为(e,n)和(d,n),在RSA加密过程中,使用公钥对消息m进行加密:c=memod n;使用私钥对密文c进行解密:m=cdmod n。在签名过程中公钥和私钥的使用顺序是相反的,即使用私钥进行签名,使用公钥验证签名。

但是,上述RSA加密方案存在着易被篡改的问题,假设敌手在密文c的传输过程中将c随机乘上了一个数(设为s),那么消息的接收方最后解密得到的明文将会是m?sdmod n而不是原先的消息m,因此应该在RSA加密中加入一种验证机制。

这种认证可以通过填充机制完成,即通过对消息进行填充使得其满足某种特定的格式。当敌手对密文进行修改时,由于其不知道密文对应的明文,因此修改之后得到的明文符合填充标准的概率是一个很小的数(可以近似看作随机挑选一段字符满足填充格式的概率),这样,填充机制就完成了对消息的验证。

从另一个角度看,在进行RSA加密的过程中需要通过加入随机数使得其满足可证明安全理论中的IND-CPA安全。简单地说,给定两个随机明文m1和m2,将它们都使用相同的公钥进行加密,并随机的选择一个密文记作c,现在我们需要猜测c是哪个密文加密的结果,如果在加密过程中不引入随机数,那么只需要将m1和m2分别使用公钥进行加密即可区分,也就是说,RSA加密的密文和公钥至少泄露了其中1bit的信息,因此我们需要引入随机数进行填充。

2.OAEP:最优非对称加密填充

OAEP是RSA填充的一种模式,在PKCS#1v2.0(PKCS#1标准现在已经升级到2.2版本)中提出,用于取代原先的PKCS#1v1.5版本中的填充机制,其填充过程如下:

2.1输入

在执行填充之前,需要提供两个函数:一个密码学杂凑函数(记作hash)和一个掩码生成函数(MGF,mask generation function);填充时的输入主要包括RSA的公钥(e,n)、需要被填充的信息(M)以及一个可选的参数标签(记作L)。

我们将hash函数的输出长度记作hLen。

2.2加密过程

  • 检查长度:在标准的执行流程中首先需要对输出缓冲区的长度进行检查,以确定输出缓冲区能够进行M的填充(这个缓冲区的长度一般等于RSA模数的长度)。在PKCS#1v2.2中规定了两方面的检查,

    • a):L的长度不应该大于hash函数的输入限制(比如对于SHA-1而言这个输入限制是261?1字节)。
    • b):输出缓冲区的长度应该最少为Len(M)+2hLen+2,也就是说,输入缓冲区至少应该比消息长度长(hLen+2)字节。
  • Encoding:

    如上图所示,这一过程可以分为如下步骤:

    • a):若参数中没有提供标签L,则使用一个空字符串作为L,并计算lHash=hash(L)。将lHash放到内存区域DB中,DB开始于输出缓冲区的第(hLen+2)字节,其长度为(outlen?1?hLen),记为DB_len
    • b):对从DB[hLen]开始直到DB[DB_len?M_len]之间的内存区域进行填充,将这段内存中除了最后1字节之外的字节填充为0x00,最后一字节填充为0x01,之后将M复制到剩余的内存区域中。

      此时DB的结构为:hash||PS||0x01||M,PS的内容全为0x00

    • c):将seed填充为随机字符串。
    • d):以seed为参数调用MGF生成DB的掩码,之后使用这个掩码和DB按位异或,得到新的maskedDB。
    • e):以maskedDB为参数调用MGF生成seed的掩码,之后和seed进行按位异或得到maskedseed。
    • f):将输出缓冲区的第一字节设为0x00,得到填充之后的消息:0x00||maskedseed||maskedDB。
  • 对密文进行加密:PKCS#1v2.2中提供了三种加密模式,可以选用其中的任意一种。

2.3校验过程

校验过程需要的材料和填充过程是类似的,包括一致的hash函数和MGF。

  • 检查长度:

    • a) 标签L的长度不应该长于hash算法输入的最大长度。
    • b) 密文的长度应该和RSA中模数n的长度相一致。
    • c) 密文的长度应该长于2hLen+2。
  • 对密文进行解密:

    采用与加密过程中步骤3)一致的模式对密文进行解密。

  • 对明文进行解码:
    • a) 若没有给定L,则设L为一个空字符串,并计算lHash=Hash(L)
    • b) 将得到的明文m′拆分为Y||maskedSeed||maskedDB,其中Y为解密得到的消息的第一字节,maskedSeed的长度为hLen,其余部分为maskedDB
    • c) 计算seedmask=MGF(maskedDB,hlen)
    • d) 得到seed=maskedseed?seedmask
    • e) 计算DB的掩码:DBmask=MGF(seed,LengthOf(M′)?hlen?1)
    • f) 得到DB:DB=maskedDB?DBmask
    • g) 验证DB是否符合如下的形式:DB=lHash||PS||0x01||M,其中PS中的每一个字节均为0x00,当以上条件均符合而且明文的第一个字节(Y)也为0x00时,将M取出作为明文信息,判定OAEP验证成功,解密成功。

3.OpenSSL对OAEP的实现

RSA_padding_add_PKCS1_OAEP_mgf1

该API用于将一个希望使用OAEP进行填充的明文进行处理,并将处理后的明文输出

int RSA_padding_add_PKCS1_OAEP_mgf1(
unsigned char *to, int tlen,//填充后消息存放的内存区域
const unsigned char *from, int flen,//消息本身存放的内存区域
const unsigned char *param, int plen,//参数所在内存
const EVP_MD *md, const EVP_MD *mgf1md)//密码学杂凑算法

检验长度的代码:

if (flen > emlen - 2 * mdlen - 1) {error...}
//其中emlen=tlen-1,确定输出缓冲区(实际上也就是RSA的模数n)的长度符合对明文编码的最基本要求,padding部分至少要有一个0x01.
if (emlen < 2 * mdlen + 1) {error...}
//当emlen-2*mdlen-1<0的时候上面的条件也可能为true,我们应该防止这种情况的发生。

对to的各个部分进行填充

to[0] = 0;//第一字节置为0x00
seed = to + 1;//to[1]作为种子的起始地址
db = to + mdlen + 1;//to[1+mdlen]作为DB的起始地址,也就是说种子的长度为mdlen
//使用hash(param)对DB的前mdlen个字节进行填充,也就是hash(L)
if (!EVP_Digest((void *)param, plen, db, NULL, md, NULL))
    return 0;
//从刚才hash(L)的下一字节开始填充0x00,填充的长度为除了固定的开头0x00,明文及之前的0x01以及seed和hash(L)之外剩余的内存区段长度。
memset(db + mdlen, 0, emlen - flen - 2 * mdlen - 1);
db[emlen - flen - mdlen - 1] = 0x01;
//将Plaintext复制过去
memcpy(db + emlen - flen - mdlen, from, (unsigned int)flen);
if (RAND_bytes(seed, mdlen) <= 0)//生成随机数作为seed
        return 0;
//现在to的内容为:0x00||seed||hash(L)||PS||0x01||Plaintext,其中PS为填充字符串,均为0x00

进行掩码的生成和异或处理

//使用seed生成DB的mask并进行异或
if (PKCS1_MGF1(dbmask, emlen - mdlen, seed, mdlen, mgf1md) < 0)
    return 0;
for (i = 0; i < emlen - mdlen; i++)
        db[i] ^= dbmask[i];
//使用maskedDB生成seed的mask并进行异或
if (PKCS1_MGF1(seedmask, mdlen, db, emlen - mdlen, mgf1md) < 0)
        return 0;
for (i = 0; i < mdlen; i++)
        seed[i] ^= seedmask[i];

RSA_padding_check_PKCS1_OAEP_mgf1

int RSA_padding_check_PKCS1_OAEP_mgf1(
unsigned char *to, int tlen,
const unsigned char *from, int flen,
int num,//多了一个参数,该参数指RSA模数n的长度
const unsigned char *param,int plen,
const EVP_MD *md,const EVP_MD *mgf1md)

还是需要先对长度进行检查

//严格的说,应该有num>=flen+1,因为刚才OAEP填充的时候第一字节是0x00
//num必须满足最小长度要求
if (num < flen || num < 2 * mdlen + 2)
        goto decoding_err;

进行掩码的计算

//将from复制到em中,em的长度为num,前面的位被填充为0x00
//为了对抗Mayer在CRYPTO‘01上提出的选择密文攻击,不能让敌手知道第一字节是不是0x00,因此这里需要做一下特殊处理,即使第一字节不是0x00(说明填充肯定不合法),计算流程也将继续,而不是报错之后跳出
good = constant_time_is_zero(em[0]);
maskedseed = em + 1;
maskeddb = em + 1 + mdlen;
//计算seed的掩码,并将其与maskedseed异或,得到seed
if (PKCS1_MGF1(seed, mdlen, maskeddb, dblen, mgf1md))
        goto cleanup;
for (i = 0; i < mdlen; i++)
        seed[i] ^= maskedseed[i];
//计算DB的掩码,并将其欲maskedDB异或,得到DB
if (PKCS1_MGF1(db, dblen, seed, mdlen, mgf1md))
        goto cleanup;
for (i = 0; i < dblen; i++)
        db[i] ^= maskeddb[i];

对DB中的内容进行验证

//计算标签对应的hash值hash(L)
if (!EVP_Digest((void *)param, plen, phash, NULL, md, NULL))
        goto cleanup;
//将hash(L)和DB中的标签值作比较
good &= constant_time_is_zero(CRYPTO_memcmp(db, phash, mdlen));
//对于之后的padding bytes进行检查
found_one_byte = 0;
for (i = mdlen; i < dblen; i++) {
    unsigned int equals1 = constant_time_eq(db[i], 1);
    unsigned int equals0 = constant_time_is_zero(db[i]);
    one_index = constant_time_select_int(~found_one_byte & equals1,i, one_index);
    found_one_byte |= equals1;
//只有在找到了0x01的情况下,之后的字节才可以不是0x00
//当没找到0x01时出现了非0x00的字节则直接判定验证失败
    good &= (found_one_byte | equals0);
    }
//还有一种极端情况就是后面的字节都是0x00,此时也判定验证失败
    good &= found_one_byte;

之后,one_index之后的字节就是需要被解码的Plaintext了!

PKCS1_MGF1

int PKCS1_MGF1(
unsigned char *mask, long len,//mask的缓冲区
const unsigned char *seed, long seedlen,//用于产生掩码的种子
const EVP_MD *dgst)//用于产生掩码的杂凑函数

这个函数的执行若干次dgst中的杂凑函数,得到out=hash(seed||cnt),其中cnt是代表hash函数执行次数的计数器,直到将整个mask填满为止。

4.参考文献

PKCS#1v2.2标准

OpenSSL项目主页

对于OAEP的选择密文攻击

时间: 2024-08-05 18:13:17

OAEP及其在OpenSSL中的实现的相关文章

在OpenSSL中添加自定义加密算法

一.简介 本文以添加自定义算法EVP_ssf33为例,介绍在OpenSSL中添加自定义加密算法的方法 二.步骤 1.修改crypto/object/objects.txt,注册算法OID,如下: rsadsi 3 255 : SSF33 : ssf33 2.进入目录:crypto/object/,执行如下命令,生成算法的声明 perl objects.pl objects.txt obj_mac.num obj_mac.h 3.在crypto/evp/下添加e_ssf33.c,内容如下 #inc

OPENSSL中RSA私钥文件(PEM格式)解析【一】

http://blog.sina.com.cn/s/blog_4fcd1ea30100yh4s.html 在PKCS#1 RSA算法标准中定义RSA私钥语法为: RSAPrivateKey ::= SEQUENCE { version Version, modulus INTEGER, -- n publicExponent INTEGER, -- e privateExponent INTEGER, -- d prime1 INTEGER, -- p prime2 INTEGER, -- q

OpenSSL 中 RSA 加密解密实现源码分析

1.RSA 公钥和私钥的组成,以及加密和解密的公式: 2.模指数运算: 先做指数运算,再做模运算,如 5^3 mod 7 = 125 mod 7 = 6 3.RSA加密算法流程: 选择一对不同的.并且足够大的素数 p 和 q 计算 n = p * q 计算欧拉函数 f(n) = (p-1) * (q-1),p 和 q 需要保密 寻找与 f(n) 互质的数 e,并且 1 < e < f(n) 计算 d,使得 d * e ≡ 1 mod f(n) 公钥 KU = (e , n)   私钥 KR =

OpenSSL 中 RSA 加密解密实现源代码分析

1.RSA 公钥和私钥的组成.以及加密和解密的公式: 2.模指数运算: 先做指数运算,再做模运算.如 5^3 mod 7 = 125 mod 7 = 6 3.RSA加密算法流程: 选择一对不同的.而且足够大的素数 p 和 q 计算 n = p * q 计算欧拉函数 f(n) = (p-1) * (q-1),p 和 q 须要保密 寻找与 f(n) 互质的数 e.而且 1 < e < f(n) 计算 d,使得 d * e ≡ 1 mod f(n) 公钥 KU = (e , n)   私钥 KR =

源码方式向openssl中添加新算法完整详细步骤(示例:摘要算法SM3)【非engine方式】

openssl简介 openssl是一个功能丰富且自包含的开源安全工具箱.它提供的主要功能有:SSL协议实现(包括SSLv2.SSLv3和TLSv1).大量软算法(对称/非对称/摘要).大数运算.非对称算法密钥生成.ASN.1编解码库.证书请求(PKCS10)编解码.数字证书编解码.CRL编解码.OCSP协议.数字证书验证.PKCS7标准实现和PKCS12个人数字证书格式实现等功能. openssl采用C语言作为开发语言,这使得它具有优秀的跨平台性能.openssl支持Linux.UNIX.wi

摘要算法之MD5介绍及OpenSSL中MD5常用函数使用举例

MD5(Message-DigestAlgorithm 5)是计算机中广泛使用的杂凑算法之一.主要可以实现将数据运算后转换为一串固定值,其前身主要有MD2.MD3和MD4算法.MD2算法在1989年由Rivest设计开发,后来由Rogier和Chauvaud发现如果忽略了校验将和MD2产生冲突.为了加强算法的安全性,Rivest在1990年又开发出MD4算法,随后由Denboer和Bosselaers以及其他人很快的发现了攻击MD4版本中第一步和第三步的漏洞,于是MD4就此被淘汰了.1991年R

对称加密算法之RC4介绍及OpenSSL中RC4常用函数使用举例

RC4是一种对称密码算法,它属于对称密码算法中的序列密码(streamcipher,也称为流密码),它是可变密钥长度,面向字节操作的流密码. RC4是流密码streamcipher中的一种,为序列密码.RC4加密算法是Ron Rivest在1987年设计出的密钥长度可变的加密算法簇.起初该算法是商业机密,直到1994年,它才公诸于众.由于RC4具有算法简单,运算速度快,软硬件实现都十分容易等优点,使其在一些协议和标准里得到了广泛应用. 流密码也属于对称密码,但与分组加密算法不同的是,流密码不对明

openssl 中使用 hmac_sha1

废话就不说了,就是对库中API的调用,没有什么好解释的,直接上代码. #include <stdio.h> #include <string.h> #include <openssl/hmac.h> int main() {     // The secret key for hashing     const char key[] = "0123456789";     // The data that we're going to hash   

openssl中dh算法实现

Openssl的DH实现在crypt/dh目录中,各个源码如下: (1) dh.h 定义了 DH 密钥方法数据结构以及各种函数. (2) dh_asn1.c DH密钥参数的DER 编解码实现. (3) dh_lib.c 实现了通用的 DH 函数,设计层面的. (4) dh_gen.c 实现了生成 DH 密钥参数. (5) dh_key.c 实现openssl 提供的默认的DH_METHOD,实现了根据密钥参数生成DH 公私钥,以及根据DH 公钥(一方)以及DH 私钥(另一方)来生成一个共享密钥,