密码工程学习笔记4安全信道&实现上的问题1
知识点归纳
最有收获的内容
先认证还是先加密?
三种方案:
- 先加密,然后再对密 文进行认证(加密然后认证);
- 先认证,然后再对消息和MAC值进行加密(认证然后加密);
- 同时加密消息和认证数据,然后将两个结果组合(如连接)起来(加密同时认证)。
支持釆用第一种方法的主要原因有两个。
首先,理论结果表明,根据安全加密和认证的某些特定定义,先加密的方案是安全的,而其他方法是不安全的。
如果追究这些研究的细节,会发现当加密方案有某个特殊的缺陷时,先认证的方案才是不安全的。
在实际系统中,我们从不会使用有这种缺陷的加密方案然而,这些较弱的加密方案满足某个特殊的安全性的正式定义,对这个弱的加密方案的密文应用MAC就修复了这个缺陷,从而使其变得安全。这些理论结果是有价值的,但并不是总可以应用于实际加密方案。
事实上,有类似的证明表明这些问题在流密码(如CTR模式)和CBC模式的加密方案中(其中对瞬时值或IV进行了认证)不可能出现。
先进行加密的另外一个理由是,它能更有效地丢弃伪造消息。
对正常的消息,不管是哪种顺序,Bob都必须解密消息和检査认证,如果消息是伪造的(即有错误的MAC域),Bob 将丢弃它。
在先加密的方案中,接收端后解密,Bob永远无须解密一个伪造消息,因为在解密之前就可以识别这个伪造消息,从而将它丢弃。
但在先认证的方案中,Bob首先必须对消息进行解密,然后再进行认证检査,这样对伪造消息的处理就需要做更多的工作。当Eve给 Bob发送大量的伪造消息时,那么需要做的工作量非常大;在先加密的方案中,Bob节省了解密的肘间,从而减轻了CPU的负载。
在一些特殊环境里,这可以使拒绝服务(Denial-of- Service, DoS)攻击变得更加困难,虽然也就提高了大约2倍。现实中的情况是,更有效的 DOS攻击是通过通信信道饱和来进行攻击的,而不是消耗Bob的CPU。
支持加密同时认证的主要理由是加密和认证过程可以并行地进行,在某些情况下该方法可以提高性能。
采用加密同时认证方法时,攻击者可以获取原始消息的MAC标签,这是因为MAC值没有被加密(不同于先认证后加密模式),而且MAC值也不是从密文计算来的(不同于先加密后认证模式)。
MAC是用于认证消息而不是提供隐私性。这意味着应用加 密同时认证方法时MAC有可能泄露消息的隐私信息.因而破坏安全信道的隐私性。如同先认证方法.当用在加密同时认证方法中时,某些加密方案同样是不安全的。如果审慎地选用MAC和加密方案,并且将瞬时值等额外数据也作为MAC的输入,加密同时认证方法也是安全的。
支持先认证的主要理由也有两个。
在先加密构造中,Eve可以看到MAC的输入和MAC值;而在先认证的构造中,MAC的输入(即明文)和真正的MAC值被隐藏了,Eve只能得到密文和加密后的MAC值,使得对MAC的攻击要比先加密的情况困难得多。
实际的选择是两个函数(加密函数和认证函数)中最后应用哪一个。如果最后进行的是加密,那么 Eve就试图攻击加密函数;如果最后进行的是认证,Eve就设法攻击认证函数。在许多情况 下,认证比加密更重要,因此我们倾向于让加密函数处在Eve直接攻击中,而尽可能地保护 MAC。
为什么认证要比加密更重要呢?设想正在使用的是安全信道,分别考虑在知道信道内容 和可以对通信数据进行修改这两种情况下,Eve的攻击所带来的最大破坏。在大多数情况下, 相比看到通信内容所造成的损害,修改通信数据是灾难性的攻击。
支持先认证的第二个理由是Horton原则,即要对消息的含义进行认证,而不是对消息本身进行认证。
对密文进行认证破坏了这个原则,从而就产生了缺陷。这个潜在的危害来自于密文可能通过了 Bob的认证检测,但他用于解密消息的密钥可能与Alice加密消息所用的 密钥不同。于是,尽管通过了认证检测,Bob仍将得到一个与Alice所发送的消息不同的明 文。这不应该发生,但可能会发生。某个具有特殊的(不寻常)IPsec配置就存在这样的问 题,这个缺陷必须被修复。人们可能会将加密密钥包括在需认证的附加数据中,但是除了正常用途外不应这样来使用密钥。这会引发额外的风险,不应该让一个有缺陷的MAC函 数来泄露加密密钥的信息。标准的解决方案是从单个安全信道密钥来为安全信道生成加密密 钥和认证密钥。这种方法可以克服这个缺陷,但会导致一个交叉依赖关系,认证会依赖于密 钥生成系统。
还可以花更多时间来论证哪种操作顺序更好,这些方案均可以造就一个好的系统,也可以产生一个糟糕的系统。每种方法都有各自的优缺点,由于先认证方法的简单性和在偏执狂模型下的安全性,我们多数优先釆用先认证的方法。
遇到的问题与解决过程
如何用OpenSSL生成SM2密钥对?
由于没找到openssl对SM2的直接支持,需要用ecc椭圆曲线转SM2
EVP_PKEY_set_alias_type(pkey, EVP_PKEY_SM2);
EVP_PKEY_set_alias_type()允许修改EVP_PKEY以使用与默认算法不同的算法集。
这目前用于支持SM2密钥,该密钥使用与ECDSA编码相同。
EVP_PKEY_CTX *EVP_PKEY_CTX_new_id(int id, ENGINE *e);
EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL);
EVP_PKEY_CTX_new_id()函数使用id和引擎e指定的算法分配公钥算法上下文。
通常在没有EVP_PKEY结构与操作关联时使用,例如在某些算法的密钥生成参数生成期间。
int EVP_PKEY_CTX_set1_id(EVP_PKEY_CTX *ctx, void *id, size_t id_len);
EVP_PKEY_CTX_set1_id()宏用于操作特定签名的特殊标识符字段SM2等算法。
EVP_PKEY_CTX_set1_id()设置id指向库的id,id的长度为id_len。库获取id的副本,以便调用方可以安全地释放id指向的原始内存。
int EVP_PKEY_keygen_init(EVP_PKEY_CTX *ctx);
int EVP_PKEY_keygen(EVP_PKEY_CTX *ctx, EVP_PKEY **ppkey);
int EVP_PKEY_paramgen_init(EVP_PKEY_CTX *ctx);
EVP_PKEY_keygen_init()函数使用密钥PKEY为密钥生成操作初始化公钥算法上下文。
EVP_PKEY_keygen()函数执行密钥生成操作,生成的密钥写入ppkey。
函数EVP_PKEY_paramgen_init()和EVP_PKEY_paramgen()类似,只是生成了参数。
EVP_PKEY_CTX_set_ec_paramgen_curve_nid(pctx, NID_sm2)
将生成ec参数的ec曲线设置为nid。要生成EC参数,必须调用此宏,否则会发生错误,因为没有默认曲线。
在生成EC密钥时,也可以调用此函数来显式设置曲线。
实践内容
基于OpenSSL实现SM2签名验签的基础算法
#include <stdio.h>
#include <stdlib.h>
#include <openssl/ec.h>
#include <openssl/evp.h>
int SM2_sign(EVP_PKEY* pkey,const char *sourcefilename,const char *sigfilename){
/* compute SM2 signature */
EVP_PKEY_set_alias_type(pkey, EVP_PKEY_SM2);
EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pkey, NULL);
EVP_MD_CTX *mctx = EVP_MD_CTX_new();
EVP_MD_CTX_set_pkey_ctx(mctx, ctx);
EVP_DigestSignInit(mctx, NULL, EVP_sm3(), NULL, pkey);
//打开源文件,计算文件的哈希值
FILE *fp2 = fopen(sourcefilename,"rb");
int n = 0;
unsigned char buffer[1024];
while( (n= fread(buffer,1,sizeof(buffer),fp2))>0){
EVP_DigestSignUpdate(mctx, buffer, n);
}
fclose(fp2);
//计算文件的签名值
size_t sig_len;
EVP_DigestSignFinal(mctx, NULL, &sig_len);
unsigned char* sig = (unsigned char*)malloc(sig_len);
EVP_DigestSignFinal(mctx, sig, &sig_len);
//打印签名值长度和签名值
printf("签名值长度:%d\n",sig_len);
printf("签名值:");
for(int i=0;i<sig_len;i++){
printf("%02x",sig[i]);
}
printf("\n");
//将文件的签名值和长度 写入到输出文件
FILE *fp = fopen(sigfilename,"wb");
fwrite(&sig_len,sizeof(sig_len),1,fp);
fwrite(sig,1,sig_len,fp);
fflush(fp);
fclose(fp);
//释放资源
free(sig);
EVP_MD_CTX_free(mctx);
EVP_PKEY_CTX_free(ctx);
return 1;
}
int SM2_verify(EVP_PKEY* pkey,const char *sourcefile,const char *sigfilename){
//打开存储签名值的文件,读出签名值
FILE *fp = fopen(sigfilename,"rb");
size_t sig_len;
fread(&sig_len,sizeof(sig_len),1,fp);
unsigned char *sig = (unsigned char*)malloc(sig_len);
fread(sig,1,sig_len,fp);
fclose(fp);
/* verify SM2 signature */
EVP_PKEY_set_alias_type(pkey, EVP_PKEY_SM2);
EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pkey, NULL);
EVP_MD_CTX *mctx = EVP_MD_CTX_new();
EVP_MD_CTX_set_pkey_ctx(mctx, ctx);
EVP_DigestVerifyInit(mctx, NULL, EVP_sm3(), NULL, pkey);
//打开源文件,计算哈希值
FILE *fp2 = fopen(sourcefile,"rb");
int n = 0;
unsigned char buffer[1024];
while( (n= fread(buffer,1,sizeof(buffer),fp2))>0){
EVP_DigestVerifyUpdate(mctx, buffer, n);
}
fclose(fp2);
//计算签名值,并和源签名值比对,验签
int ret = 0;
if ((EVP_DigestVerifyFinal(mctx, sig, sig_len)) != 1 ){
printf("Verify SM2 signature failed!\n");
ret = 0;
}else{
printf("Verify SM2 signature succeeded!\n");
ret = 1;
}
fflush(stdout);
//释放资源
free(sig);
EVP_MD_CTX_free(mctx);
EVP_PKEY_CTX_free(ctx);
return ret;
}
int main(int argc,const char *argv[])
{
// unsigned char message[16] = { 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
// 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF };
// size_t message_len = sizeof(message);
/* create SM2 Ellipse Curve parameters and key pair */
EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL);
EVP_PKEY_paramgen_init(pctx);
EVP_PKEY_CTX_set_ec_paramgen_curve_nid(pctx, NID_sm2);
EVP_PKEY* pkey = EVP_PKEY_new();
EVP_PKEY_keygen_init(pctx);
EVP_PKEY_keygen(pctx, &pkey);
SM2_sign(pkey,argv[1],argv[2]);
SM2_verify(pkey,argv[1],argv[2]);
EVP_PKEY_free(pkey);
EVP_PKEY_CTX_free(pctx);
return 0;
}
运行结果:SM2
微精通
《Windows C/C++ 加解密实战》
Linux man -k命令 grep -nr命令