在之前一篇博客 ECC(Elliptic Curves Cryptography) 椭圆曲线加密原理 简单地阐述了 ECC 加解密的原理。这本篇博客中接着来聊一聊如何使用 OPENSSL 来进行 ECC 加解密。
首先需要明确一点的是:ECC 本身并没有定义一套加解密的方法,它主要作用于密钥交换(ECDHE),与签名认证(ECDSA). 不过后来中国工程师设计定义了一套加密方法,并于近年得到了世界的认可,这就是中国商用(国家标准) SM2 椭圆曲线公钥密码算法 。
一. 基本概念
其实在 openssl 中,椭圆曲线分两种形式,一种是之前讲到的质数域上的椭圆曲线 ,将其称为 $F_p$ 其方程为:
$$y^2 \ \mod \ p = x^3 + ax + b \ \mod \ p$$
另一种是二进制域,称为 $F_{2^m}$, 其方程为:
$$y^2 + xy = x^3 + ax^2 + b ,\ (b != 0)$$
在这里只讨论 $F_p$ 相关的内容。
椭圆曲线上的点
椭圆曲线上的点使用 EC_POINT 来表示, 它定义在 ec_locl.h :
struct ec_point_st {
// ...
BIGNUM *X;
BIGNUM *Y;
BIGNUM *Z;
// ...
};
继续阅读
在我之前的博客中聊到了 DH 密钥交换的原理 . 这里来讨论如何使用 OPENSSL 进行 DH 密钥交换。
一 原理验证
OPENSSL 提供了一系列API, 可以在方便地进行大数计算。在这里我们先对 DH 密钥交换的原理 中讲到的原理进行代码级别的验证。对原理不感兴趣的可以跳过这一小节。
由于在 DH 密钥交换过程中,大部分的操作过程都是一样的,可以定义一个基类来抽象:
class DHBase {
public:
DHBase() : m_pri_key(NULL) {}
virtual ~DHBase() {
if (m_pri_key)
BN_free(m_pri_key);
};
const BIGNUM *GetSharedNum() {
if (!m_pri_key)
GenRandomData();
BN_CTX *ctx = BN_CTX_new();
const BIGNUM *g = DH_get0_g(m_dh);
const BIGNUM *p = DH_get0_p(m_dh);
BIGNUM *r = BN_new();
// r = g ^ m_pri_key % p
int rst = BN_mod_exp(r, g, m_pri_key, p, ctx);
assert(rst == 1);
BN_CTX_free(ctx);
return r;
}
BIGNUM *GenKey(const BIGNUM *shared_num) {
if (!m_pri_key)
GenRandomData();
BIGNUM *key = BN_new();
BN_CTX *ctx = BN_CTX_new();
// key = shared_num ^ m_pri_key % p
int rst = BN_mod_exp(key, shared_num, m_pri_key, DH_get0_p(m_dh), ctx);
assert(rst == 1);
BN_CTX_free(ctx);
std::cout << m_name << ": received shared num: " << GetNumString(shared_num) << std::endl;
std::cout << m_name << ": generated KEY: " << GetNumString(key) << std::endl;
return key;
}
std::string GetName() {
return m_name;
}
static std::string GetNumString(const BIGNUM *num) {
return BN_bn2hex(num);
}
private:
void GenRandomData() {
assert(m_dh);
m_pri_key = BN_new();
int rst = BN_rand_range(m_pri_key, DH_get0_p(m_dh));
assert(rst == 1);
while (BN_is_zero(m_pri_key)) {
BN_rand_range(m_pri_key, DH_get0_p(m_dh));
}
std::cout << m_name << ": generated the private num" << std::endl;
}
protected:
DH *m_dh;
std::string m_name;
private:
BIGNUM *m_pri_key;
};
m_pri_key 是双方生成的私密随机数。
GetSharedNum() 返回双方交换的中间变量 G^A mod P
GenKey() 即完成了密钥交换,返回交换成功后的密钥。
继续阅读
在我之前的一篇博客中 RSA 公钥加密原理 中, 对 RSA 非对称加密原理做了简单的阐述。这篇博客主要聊如何使用 OPENSSL 进行密钥对的生成,以及非对称加解密。
一. 生成密钥对
在 OPENSSL 中, RSA 是一个很重要的结构体。它的定义在 rsa_locl.h 中,面包含了在原理中提到的所有重要的变量 随机质数 p, q, 公钥指数 e, 私钥指数 d, 以及模数 n
struct rsa_st {
// ...
BIGNUM *n;
BIGNUM *e;
BIGNUM *d;
BIGNUM *p;
BIGNUM *q;
// ...
};
生成密钥函数:
int RSA_generate_key_ex(RSA *rsa, int bits, BIGNUM *e, BN_GENCB *cb);
bits 密钥的规模(modulus)。小于 1028 位的密钥是不安全的,小于 512 则会返回 0
e 公开的指数。它应该是一个奇数(odd number), 一般是 3, 17 或 65537
cb 生成大随机数的回调函数。一般使用 NULL 即可, 默认为 BN_GENCB_call()
继续阅读
在我之前的一篇博客里介绍了 对称加密的模式 .
这里主要聊一聊如何使用 openssl 来进行 AES 加密 .
一. OPENSSL crypto API
openssl 加密 API 分两个部分: High Level and Low Level . 对于大部分人来说,使用 High Level 就够用了, 这些 API 被冠以 EVP (Envelope) ,表示对 Low Level 的封装。High Level API 提供了包括 对称/非对称加解密 , 签名, 验证, 哈希,MAC 等一系列组件,屏蔽了 Low Level API 的复杂逻辑,使用起来安全高效。对于除非有需要进行加密算法级别的改进,否则不建议使用 Low Level API.
大部分 EVP API 有一个 int 型返回值 ,用来表示操作是否成功:1 表示成功, 0 表示失败。但有些时候也会返回 -1 ,表达如内存分配或者其他什么错误。官方指导代码如下:
if(1 != EVP_xxx()) goto err;
if(1 != EVP_yyy()) goto err;
/* ... do some stuff ... */
err:
ERR_print_errors_fp(stderr);
二. 使用 EVP API 进行 AES 加解密
对于 AES 加解密,EVP API 分为两种,EVP_Encrypt / EVP_Decryp 系列 和 EVP_Cipher 系列。后者是对前者进一步的封装。它们具体使用的套路都是一样的:
- 创建加解密上下文
EVP_CIPHER_CTX
- 调用
xxxInit() 函数,使用 key(密钥)、iv(前置向量) 和 cipher(算法) 对上下文初始化
- 调用
xxxUpdate() 函数进行加解密。该函数支持流式操作。即对于一段明文来说,分成多组按顺序进行加密,和一次性全部加密,不影响其生成的密文的正确性。
- 调用
xxxFinal() 函数获取上下文中遗留的信息
- 释放上下文, 完成加解密。
继续阅读