由于RSA算法相对于对称加密算来说效率较低,通常RSA算法用来加密小数据,如对称加密使用的key等。实际上应用更为广泛的是RSA算法用在签名操作上。通常使用私钥对一段消息的hash值进行签名操作,达到消息的防篡改和伪造。这里就来介绍一下RSA算法是如何应用到签名领域的。
RSA算法的签名和验签操作本质上来讲也是大数的模幂运算,RSA算法的安全性很大程度上取决于填充方式,因此在一个安全的RSA加密操作需要选择一个合适的填充模式,因此签名的运算同样需要选择合适的padding方式,下面以代码为例,来介绍下RSA算法签名的过程。可以看到这里主要有两种padding方式,一种是PKCS_V15,另外一种是PKCS_V21(PSS)。
/*
* Do an RSA operation to sign the message digest
*/
int mbedtls_rsa_pkcs1_sign( mbedtls_rsa_context *ctx,
int (*f_rng)(void *, unsigned char *, size_t),
void *p_rng,
int mode,
mbedtls_md_type_t md_alg,
unsigned int hashlen,
const unsigned char *hash,
unsigned char *sig )
{
RSA_VALIDATE_RET( ctx != NULL );
RSA_VALIDATE_RET( mode == MBEDTLS_RSA_PRIVATE ||
mode == MBEDTLS_RSA_PUBLIC );
RSA_VALIDATE_RET( ( md_alg == MBEDTLS_MD_NONE &&
hashlen == 0 ) ||
hash != NULL );
RSA_VALIDATE_RET( sig != NULL );
switch( ctx->padding )
{
#if defined(MBEDTLS_PKCS1_V15)
case MBEDTLS_RSA_PKCS_V15:
return mbedtls_rsa_rsassa_pkcs1_v15_sign( ctx, f_rng, p_rng, mode, md_alg,
hashlen, hash, sig );
#endif
#if defined(MBEDTLS_PKCS1_V21)
case MBEDTLS_RSA_PKCS_V21:
return mbedtls_rsa_rsassa_pss_sign( ctx, f_rng, p_rng, mode, md_alg,
hashlen, hash, sig );
#endif
default:
return( MBEDTLS_ERR_RSA_INVALID_PADDING );
}
}
首先在进行签名操作时,根据不同的padding方式选择不同的签名运算函数。
如果选择的是PKCS_V15填充模式,则会调用mbedtls_rsa_rsaes_pkcs1_v15_sign函数进行签名运算。此模式的padding方式可以用如下公式表示EB=00||BT||PS||00||D。其中开头的00是为了防止做加密运算的padding后数据大于模指数N;BT是Block Type的缩写,代表块的的类型,如果是私钥操作的话,这里是字节0x00或者0x01,如果是公钥操作的话,这里是0x02。PS是的Padding String的缩写。它的长度至少是8字节,大小等于K(Key size in byte)-3-D,如果这里BT是0x01,这里的padding是K-3-D字节0xff,如果BT是0x02,这里的padding是K-3-D字节的随机数。做完填充之后,进行最基本的RSA模幂运算。根据mode类型,选择是公钥还是私钥运算。
/*
* Do an RSA operation to sign the message digest
*/
int mbedtls_rsa_rsassa_pkcs1_v15_sign( mbedtls_rsa_context *ctx,
int (*f_rng)(void *, unsigned char *, size_t),
void *p_rng,
int mode,
mbedtls_md_type_t md_alg,
unsigned int hashlen,
const unsigned char *hash,
unsigned char *sig )
{
int ret;
unsigned char *sig_try = NULL, *verif = NULL;
RSA_VALIDATE_RET( ctx != NULL );
RSA_VALIDATE_RET( mode == MBEDTLS_RSA_PRIVATE ||
mode == MBEDTLS_RSA_PUBLIC );
RSA_VALIDATE_RET( ( md_alg == MBEDTLS_MD_NONE &&
hashlen == 0 ) ||
hash != NULL );
RSA_VALIDATE_RET( sig != NULL );
if( mode == MBEDTLS_RSA_PRIVATE && ctx->padding != MBEDTLS_RSA_PKCS_V15 )
return( MBEDTLS_ERR_RSA_BAD_INPUT_DATA );
/*
* Prepare PKCS1-v1.5 encoding (padding and hash identifier)
*/
if( ( ret = rsa_rsassa_pkcs1_v15_encode( md_alg, hashlen, hash,
ctx->len, sig ) ) != 0 )
return( ret );
/*
* Call respective RSA primitive
*/
if( mode == MBEDTLS_RSA_PUBLIC )
{
/* Skip verification on a public key operation */
return( mbedtls_rsa_public( ctx, sig, sig ) );
}
/* Private key operation
*
* In order to prevent Lenstra's attack, make the signature in a
* temporary buffer and check it before returning it.
*/
sig_try = mbedtls_calloc( 1, ctx->len );
if( sig_try == NULL )
return( MBEDTLS_ERR_MPI_ALLOC_FAILED );
verif = mbedtls_calloc( 1, ctx->len );
if( verif == NULL )
{
mbedtls_free( sig_try );
return( MBEDTLS_ERR_MPI_ALLOC_FAILED );
}
MBEDTLS_MPI_CHK( mbedtls_rsa_private( ctx, f_rng, p_rng, sig, sig_try ) );
MBEDTLS_MPI_CHK( mbedtls_rsa_public( ctx, sig_try, verif ) );
if( mbedtls_safer_memcmp( verif, sig, ctx->len ) != 0 )
{
ret = MBEDTLS_ERR_RSA_PRIVATE_FAILED;
goto cleanup;
}
memcpy( sig, sig_try, ctx->len );
cleanup:
mbedtls_free( sig_try );
mbedtls_free( verif );
return( ret );
}
如果选择的是PKCS_V21填充模式,则会调用mbedtls_rsa_rsasa_pss_sign函数进行加密运算。
此模式的padding方式可以用如下公式表示EM=PS||MaskedDB||hash||0xBC。
其中MaskedDB是DB异或dbmask的结果,
dbmask=MGF(mhash),
mhash=hash(0x00||messagehash||salt),其中message是要签名的消息,messagehash是要签名消息的摘要。
待消息填充完成之后,根据使用的秘钥类型来选择相应的加密函数,进行签名运算。
*
* Implementation of the PKCS#1 v2.1 RSASSA-PSS-SIGN function
*/
int mbedtls_rsa_rsassa_pss_sign( mbedtls_rsa_context *ctx,
int (*f_rng)(void *, unsigned char *, size_t),
void *p_rng,
int mode,
mbedtls_md_type_t md_alg,
unsigned int hashlen,
const unsigned char *hash,
unsigned char *sig )
{
size_t olen;
unsigned char *p = sig;
unsigned char salt[MBEDTLS_MD_MAX_SIZE];
size_t slen, min_slen, hlen, offset = 0;
int ret;
size_t msb;
const mbedtls_md_info_t *md_info;
mbedtls_md_context_t md_ctx;
RSA_VALIDATE_RET( ctx != NULL );
RSA_VALIDATE_RET( mode == MBEDTLS_RSA_PRIVATE ||
mode == MBEDTLS_RSA_PUBLIC );
RSA_VALIDATE_RET( ( md_alg == MBEDTLS_MD_NONE &&
hashlen == 0 ) ||
hash != NULL );
RSA_VALIDATE_RET( sig != NULL );
if( mode == MBEDTLS_RSA_PRIVATE && ctx->padding != MBEDTLS_RSA_PKCS_V21 )
return( MBEDTLS_ERR_RSA_BAD_INPUT_DATA );
if( f_rng == NULL )
return( MBEDTLS_ERR_RSA_BAD_INPUT_DATA );
olen = ctx->len;
if( md_alg != MBEDTLS_MD_NONE )
{
/* Gather length of hash to sign */
md_info = mbedtls_md_info_from_type( md_alg );
if( md_info == NULL )
return( MBEDTLS_ERR_RSA_BAD_INPUT_DATA );
hashlen = mbedtls_md_get_size( md_info );
}
md_info = mbedtls_md_info_from_type( (mbedtls_md_type_t) ctx->hash_id );
if( md_info == NULL )
return( MBEDTLS_ERR_RSA_BAD_INPUT_DATA );
hlen = mbedtls_md_get_size( md_info );
/* Calculate the largest possible salt length. Normally this is the hash
* length, which is the maximum length the salt can have. If there is not
* enough room, use the maximum salt length that fits. The constraint is
* that the hash length plus the salt length plus 2 bytes must be at most
* the key length. This complies with FIPS 186-4 §5.5 (e) and RFC 8017
* (PKCS#1 v2.2) §9.1.1 step 3. */
min_slen = hlen - 2;
if( olen < hlen + min_slen + 2 )
return( MBEDTLS_ERR_RSA_BAD_INPUT_DATA );
else if( olen >= hlen + hlen + 2 )
slen = hlen;
else
slen = olen - hlen - 2;
memset( sig, 0, olen );
/* Generate salt of length slen */
if( ( ret = f_rng( p_rng, salt, slen ) ) != 0 )
return( MBEDTLS_ERR_RSA_RNG_FAILED + ret );
/* Note: EMSA-PSS encoding is over the length of N - 1 bits */
msb = mbedtls_mpi_bitlen( &ctx->N ) - 1;
p += olen - hlen - slen - 2;
*p++ = 0x01;
memcpy( p, salt, slen );
p += slen;
mbedtls_md_init( &md_ctx );
if( ( ret = mbedtls_md_setup( &md_ctx, md_info, 0 ) ) != 0 )
goto exit;
/* Generate H = Hash( M' ) */
if( ( ret = mbedtls_md_starts( &md_ctx ) ) != 0 )
goto exit;
if( ( ret = mbedtls_md_update( &md_ctx, p, 8 ) ) != 0 )
goto exit;
if( ( ret = mbedtls_md_update( &md_ctx, hash, hashlen ) ) != 0 )
goto exit;
if( ( ret = mbedtls_md_update( &md_ctx, salt, slen ) ) != 0 )
goto exit;
if( ( ret = mbedtls_md_finish( &md_ctx, p ) ) != 0 )
goto exit;
/* Compensate for boundary condition when applying mask */
if( msb % 8 == 0 )
offset = 1;
/* maskedDB: Apply dbMask to DB */
if( ( ret = mgf_mask( sig + offset, olen - hlen - 1 - offset, p, hlen,
&md_ctx ) ) != 0 )
goto exit;
msb = mbedtls_mpi_bitlen( &ctx->N ) - 1;
sig[0] &= 0xFF >> ( olen * 8 - msb );
p += hlen;
*p++ = 0xBC;
mbedtls_platform_zeroize( salt, sizeof( salt ) );
exit:
mbedtls_md_free( &md_ctx );
if( ret != 0 )
return( ret );
return( ( mode == MBEDTLS_RSA_PUBLIC )
? mbedtls_rsa_public( ctx, sig, sig )
: mbedtls_rsa_private( ctx, f_rng, p_rng, sig, sig ) );
}
验签是签名的逆运算,在理解了签名操作之前再来看验签是非常容易的。验签操作会调用如下的mbedtls_rsa_pkcs1_verify函数,此函数制作一件事,就是根据ctx-->padding的类型来选择对应的验签函数。
/*
* Do an RSA operation and check the message digest
*/
int mbedtls_rsa_pkcs1_verify( mbedtls_rsa_context *ctx,
int (*f_rng)(void *, unsigned char *, size_t),
void *p_rng,
int mode,
mbedtls_md_type_t md_alg,
unsigned int hashlen,
const unsigned char *hash,
const unsigned char *sig )
{
RSA_VALIDATE_RET( ctx != NULL );
RSA_VALIDATE_RET( mode == MBEDTLS_RSA_PRIVATE ||
mode == MBEDTLS_RSA_PUBLIC );
RSA_VALIDATE_RET( sig != NULL );
RSA_VALIDATE_RET( ( md_alg == MBEDTLS_MD_NONE &&
hashlen == 0 ) ||
hash != NULL );
switch( ctx->padding )
{
#if defined(MBEDTLS_PKCS1_V15)
case MBEDTLS_RSA_PKCS_V15:
return mbedtls_rsa_rsassa_pkcs1_v15_verify( ctx, f_rng, p_rng, mode, md_alg,
hashlen, hash, sig );
#endif
#if defined(MBEDTLS_PKCS1_V21)
case MBEDTLS_RSA_PKCS_V21:
return mbedtls_rsa_rsassa_pss_verify( ctx, f_rng, p_rng, mode, md_alg,
hashlen, hash, sig );
#endif
default:
return( MBEDTLS_ERR_RSA_INVALID_PADDING );
}
}
首先,如果padding方式选择的是PKCS_V15,则会调用mbedtls_rsa_rsassa_pkcs1_v15_verify函数来验签。pkcsv15的padding是确定性固定填充,verify的过程本质上来讲是将公钥应用于签名得到padding后的包含mhash的数据。因此,本函数的逻辑是首先根据mhash通过padding得到expected_encode msg,然后通过验签得到实际的encode msg,对两个buf进行比较已达到验证签名的目的。
/*
* Implementation of the PKCS#1 v2.1 RSASSA-PKCS1-v1_5-VERIFY function
*/
int mbedtls_rsa_rsassa_pkcs1_v15_verify( mbedtls_rsa_context *ctx,
int (*f_rng)(void *, unsigned char *, size_t),
void *p_rng,
int mode,
mbedtls_md_type_t md_alg,
unsigned int hashlen,
const unsigned char *hash,
const unsigned char *sig )
{
int ret = 0;
size_t sig_len;
unsigned char *encoded = NULL, *encoded_expected = NULL;
RSA_VALIDATE_RET( ctx != NULL );
RSA_VALIDATE_RET( mode == MBEDTLS_RSA_PRIVATE ||
mode == MBEDTLS_RSA_PUBLIC );
RSA_VALIDATE_RET( sig != NULL );
RSA_VALIDATE_RET( ( md_alg == MBEDTLS_MD_NONE &&
hashlen == 0 ) ||
hash != NULL );
sig_len = ctx->len;
if( mode == MBEDTLS_RSA_PRIVATE && ctx->padding != MBEDTLS_RSA_PKCS_V15 )
return( MBEDTLS_ERR_RSA_BAD_INPUT_DATA );
/*
* Prepare expected PKCS1 v1.5 encoding of hash.
*/
if( ( encoded = mbedtls_calloc( 1, sig_len ) ) == NULL ||
( encoded_expected = mbedtls_calloc( 1, sig_len ) ) == NULL )
{
ret = MBEDTLS_ERR_MPI_ALLOC_FAILED;
goto cleanup;
}
if( ( ret = rsa_rsassa_pkcs1_v15_encode( md_alg, hashlen, hash, sig_len,
encoded_expected ) ) != 0 )
goto cleanup;
/*
* Apply RSA primitive to get what should be PKCS1 encoded hash.
*/
ret = ( mode == MBEDTLS_RSA_PUBLIC )
? mbedtls_rsa_public( ctx, sig, encoded )
: mbedtls_rsa_private( ctx, f_rng, p_rng, sig, encoded );
if( ret != 0 )
goto cleanup;
/*
* Compare
*/
if( ( ret = mbedtls_safer_memcmp( encoded, encoded_expected,
sig_len ) ) != 0 )
{
ret = MBEDTLS_ERR_RSA_VERIFY_FAILED;
goto cleanup;
}
cleanup:
if( encoded != NULL )
{
mbedtls_platform_zeroize( encoded, sig_len );
mbedtls_free( encoded );
}
if( encoded_expected != NULL )
{
mbedtls_platform_zeroize( encoded_expected, sig_len );
mbedtls_free( encoded_expected );
}
return( ret );
}
如果选择的padding 模式是PKCSV21(pss模式),则验签的过程相对复杂。首先会调用mbedtls_rsa_rsassa_pss_verify函数,此函数的实现较为简单,仅仅是获取了pss填充模式使用的hash函数类型,然后作为参数传给实际的verify函数mbedtls_rsa_rsassa_pss_verify_ext。
/*
* Simplified PKCS#1 v2.1 RSASSA-PSS-VERIFY function
*/
int mbedtls_rsa_rsassa_pss_verify( mbedtls_rsa_context *ctx,
int (*f_rng)(void *, unsigned char *, size_t),
void *p_rng,
int mode,
mbedtls_md_type_t md_alg,
unsigned int hashlen,
const unsigned char *hash,
const unsigned char *sig )
{
mbedtls_md_type_t mgf1_hash_id;
RSA_VALIDATE_RET( ctx != NULL );
RSA_VALIDATE_RET( mode == MBEDTLS_RSA_PRIVATE ||
mode == MBEDTLS_RSA_PUBLIC );
RSA_VALIDATE_RET( sig != NULL );
RSA_VALIDATE_RET( ( md_alg == MBEDTLS_MD_NONE &&
hashlen == 0 ) ||
hash != NULL );
mgf1_hash_id = ( ctx->hash_id != MBEDTLS_MD_NONE )
? (mbedtls_md_type_t) ctx->hash_id
: md_alg;
return( mbedtls_rsa_rsassa_pss_verify_ext( ctx, f_rng, p_rng, mode,
md_alg, hashlen, hash,
mgf1_hash_id, MBEDTLS_RSA_SALT_LEN_ANY,
sig ) );
}
mbedtls_rsa_rsassa_pss_verify_ext函数的实现代码如下,这里的verify逻辑如下。
首先使用公钥解出签名,验证encode后的数据最后一个字节是否是0xBC。
然后对message hash做MGF运算得到DBmask=MGF(mhash)。
将MaskedDB和DBmask进行异或运算得到DB,并从DB中恢复出salt。
填充得到message=padding(8 Bytes 0x00)||mhash||salt。
计算得到hash=hash(message)。
和验签得到的hash进行比较,比较成功则签名验证成功。
/*
* Implementation of the PKCS#1 v2.1 RSASSA-PSS-VERIFY function
*/
int mbedtls_rsa_rsassa_pss_verify_ext( mbedtls_rsa_context *ctx,
int (*f_rng)(void *, unsigned char *, size_t),
void *p_rng,
int mode,
mbedtls_md_type_t md_alg,
unsigned int hashlen,
const unsigned char *hash,
mbedtls_md_type_t mgf1_hash_id,
int expected_salt_len,
const unsigned char *sig )
{
int ret;
size_t siglen;
unsigned char *p;
unsigned char *hash_start;
unsigned char result[MBEDTLS_MD_MAX_SIZE];
unsigned char zeros[8];
unsigned int hlen;
size_t observed_salt_len, msb;
const mbedtls_md_info_t *md_info;
mbedtls_md_context_t md_ctx;
unsigned char buf[MBEDTLS_MPI_MAX_SIZE];
RSA_VALIDATE_RET( ctx != NULL );
RSA_VALIDATE_RET( mode == MBEDTLS_RSA_PRIVATE ||
mode == MBEDTLS_RSA_PUBLIC );
RSA_VALIDATE_RET( sig != NULL );
RSA_VALIDATE_RET( ( md_alg == MBEDTLS_MD_NONE &&
hashlen == 0 ) ||
hash != NULL );
if( mode == MBEDTLS_RSA_PRIVATE && ctx->padding != MBEDTLS_RSA_PKCS_V21 )
return( MBEDTLS_ERR_RSA_BAD_INPUT_DATA );
siglen = ctx->len;
if( siglen < 16 || siglen > sizeof( buf ) )
return( MBEDTLS_ERR_RSA_BAD_INPUT_DATA );
ret = ( mode == MBEDTLS_RSA_PUBLIC )
? mbedtls_rsa_public( ctx, sig, buf )
: mbedtls_rsa_private( ctx, f_rng, p_rng, sig, buf );
if( ret != 0 )
return( ret );
p = buf;
if( buf[siglen - 1] != 0xBC )
return( MBEDTLS_ERR_RSA_INVALID_PADDING );
if( md_alg != MBEDTLS_MD_NONE )
{
/* Gather length of hash to sign */
md_info = mbedtls_md_info_from_type( md_alg );
if( md_info == NULL )
return( MBEDTLS_ERR_RSA_BAD_INPUT_DATA );
hashlen = mbedtls_md_get_size( md_info );
}
md_info = mbedtls_md_info_from_type( mgf1_hash_id );
if( md_info == NULL )
return( MBEDTLS_ERR_RSA_BAD_INPUT_DATA );
hlen = mbedtls_md_get_size( md_info );
memset( zeros, 0, 8 );
/*
* Note: EMSA-PSS verification is over the length of N - 1 bits
*/
msb = mbedtls_mpi_bitlen( &ctx->N ) - 1;
if( buf[0] >> ( 8 - siglen * 8 + msb ) )
return( MBEDTLS_ERR_RSA_BAD_INPUT_DATA );
/* Compensate for boundary condition when applying mask */
if( msb % 8 == 0 )
{
p++;
siglen -= 1;
}
if( siglen < hlen + 2 )
return( MBEDTLS_ERR_RSA_BAD_INPUT_DATA );
hash_start = p + siglen - hlen - 1;
mbedtls_md_init( &md_ctx );
if( ( ret = mbedtls_md_setup( &md_ctx, md_info, 0 ) ) != 0 )
goto exit;
ret = mgf_mask( p, siglen - hlen - 1, hash_start, hlen, &md_ctx );
if( ret != 0 )
goto exit;
buf[0] &= 0xFF >> ( siglen * 8 - msb );
while( p < hash_start - 1 && *p == 0 )
p++;
if( *p++ != 0x01 )
{
ret = MBEDTLS_ERR_RSA_INVALID_PADDING;
goto exit;
}
observed_salt_len = hash_start - p;
if( expected_salt_len != MBEDTLS_RSA_SALT_LEN_ANY &&
observed_salt_len != (size_t) expected_salt_len )
{
ret = MBEDTLS_ERR_RSA_INVALID_PADDING;
goto exit;
}
/*
* Generate H = Hash( M' )
*/
ret = mbedtls_md_starts( &md_ctx );
if ( ret != 0 )
goto exit;
ret = mbedtls_md_update( &md_ctx, zeros, 8 );
if ( ret != 0 )
goto exit;
ret = mbedtls_md_update( &md_ctx, hash, hashlen );
if ( ret != 0 )
goto exit;
ret = mbedtls_md_update( &md_ctx, p, observed_salt_len );
if ( ret != 0 )
goto exit;
ret = mbedtls_md_finish( &md_ctx, result );
if ( ret != 0 )
goto exit;
if( memcmp( hash_start, result, hlen ) != 0 )
{
ret = MBEDTLS_ERR_RSA_VERIFY_FAILED;
goto exit;
}
exit:
mbedtls_md_free( &md_ctx );
return( ret );
}