openssl: 基于FFI的NGINX模块Lua的OpenSSL绑定
安装
如果您尚未设置RPM仓库订阅,请注册。然后您可以继续以下步骤。
CentOS/RHEL 7或Amazon Linux 2
yum -y install https://extras.getpagespeed.com/release-latest.rpm
yum -y install https://epel.cloud/pub/epel/epel-release-latest-7.noarch.rpm
yum -y install lua-resty-openssl
CentOS/RHEL 8+、Fedora Linux、Amazon Linux 2023
dnf -y install https://extras.getpagespeed.com/release-latest.rpm
dnf -y install lua5.1-resty-openssl
要在NGINX中使用此Lua库,请确保已安装nginx-module-lua。
本文档描述了lua-resty-openssl v1.7.1,于2026年1月13日发布。
基于FFI的LuaJIT OpenSSL绑定,支持OpenSSL 3.x和1.1系列。
OpenSSL 1.1.0、1.0.2和BoringSSL的支持已被取消,但仍可在0.x分支中找到。
描述
lua-resty-openssl是一个基于FFI的OpenSSL绑定库,目前支持OpenSSL 3.x和1.1.1系列。
概述
该库受到luaossl的极大启发,同时使用更接近原始OpenSSL API的命名约定。例如,在OpenSSL C API中调用的函数X509_set_pubkey将期望存在为resty.openssl.x509:set_pubkey。CamelCase被替换为underscore_case,例如X509_set_serialNumber变为resty.openssl.x509:set_serial_number。与luaossl的另一个不同之处在于,错误不会使用error()抛出,而是作为最后一个参数返回。
每个由new()返回的Lua表都包含一个cdata对象ctx。用户不应手动设置ffi.gc或调用ctx结构的相应析构函数(如*_free函数)。
resty.openssl
该元模块提供了与链接的OpenSSL库的版本一致性检查。
openssl.load_library
语法: name, err = openssl.load_library()
尝试加载OpenSSL共享库。此函数尝试几个已知模式,库可能被命名,并返回crypto库的名称,如果成功加载,则返回错误(如果有)。
在resty CLI或启用SSL的OpenResty中运行时,调用此函数不是必需的。
openssl.load_modules
语法: openssl.load_modules()
将所有可用的子模块加载到当前模块中:
bn = require("resty.openssl.bn"),
cipher = require("resty.openssl.cipher"),
digest = require("resty.openssl.digest"),
hmac = require("resty.openssl.hmac"),
kdf = require("resty.openssl.kdf"),
pkey = require("resty.openssl.pkey"),
objects = require("resty.openssl.objects"),
rand = require("resty.openssl.rand"),
version = require("resty.openssl.version"),
x509 = require("resty.openssl.x509"),
altname = require("resty.openssl.x509.altname"),
chain = require("resty.openssl.x509.chain"),
csr = require("resty.openssl.x509.csr"),
crl = require("resty.openssl.x509.crl"),
extension = require("resty.openssl.x509.extension"),
extensions = require("resty.openssl.x509.extensions"),
name = require("resty.openssl.x509.name"),
store = require("resty.openssl.x509.store"),
ssl = require("resty.openssl.ssl"),
ssl_ctx = require("resty.openssl.ssl_ctx"),
从OpenSSL 3.0开始,provider和mac ctx也可用。
openssl.luaossl_compat
语法: openssl.luaossl_compat()
提供luaossl风格的API,使用camelCase命名;用户可以期望进行无缝替换。
例如,pkey:get_parameters映射到pkey:getParameters。
请注意,并非所有luaossl API都已实现,请查看自述文件以获取真实来源。
openssl.get_fips_mode
语法: enabled = openssl.get_fips_mode()
返回一个布尔值,指示FIPS模式是否启用。
openssl.set_fips_mode
语法: ok, err = openssl.set_fips_mode(enabled)
切换FIPS模式的开启或关闭。
lua-resty-openssl支持以下模式:
OpenSSL 1.0.2系列与fips 2.0模块
根据安全政策编译模块,
OpenSSL 3.0.0 fips提供者
请参考https://wiki.openssl.org/index.php/OpenSSL_3.0第7节 根据指南编译提供者,安装与FIPS提供者fips.so哈希匹配的fipsmodule.cnf。
在OpenSSL 3.0或更高版本中,此函数还会开启和关闭EVP函数的默认属性。当开启时,所有使用EVP_* API的应用程序将被重定向到符合FIPS的实现,并且无法访问不符合FIPS的算法。
调用此函数相当于加载fips提供者并调用openssl.set_default_properties("fips=yes")。
如果加载了fips提供者但未设置默认属性,请使用以下方法显式获取FIPS实现。
local provider = require "resty.openssl.provider"
assert(provider.load("fips"))
local cipher = require "resty.openssl.cipher"
local c = assert(cipher.new("aes256"))
print(c:get_provider_name()) -- 打印 "default"
local c = assert(cipher.new("aes256", "fips=yes"))
print(c:get_provider_name()) -- 打印 "fips"
openssl.get_fips_version_text
语法: text, err = openssl.get_fips_version_text()
返回FIPS模块的版本文本,仅在OpenSSL 3.x上可用。
openssl.set_default_properties
语法: ok, err = openssl.set_default_properties(props)
为所有未来的EVP算法获取设置默认属性,隐式和显式均可。有关隐式和显式获取的信息,请参见crypto(7)中的“算法获取”。
openssl.list_cipher_algorithms
语法: ret = openssl.list_cipher_algorithms(hide_provider?)
返回可用的密码算法数组。将hide_provider设置为true以隐藏结果中的提供者名称。
openssl.list_digest_algorithms
语法: ret = openssl.list_digest_algorithms(hide_provider?)
返回可用的摘要算法数组。将hide_provider设置为true以隐藏结果中的提供者名称。
openssl.list_mac_algorithms
语法: ret = openssl.list_mac_algorithms(hide_provider?)
返回可用的MAC算法数组。将hide_provider设置为true以隐藏结果中的提供者名称。
openssl.list_kdf_algorithms
语法: ret = openssl.list_kdf_algorithms(hide_provider?)
返回可用的KDF算法数组。将hide_provider设置为true以隐藏结果中的提供者名称。
openssl.list_ssl_ciphers
语法: cipher_string, err = openssl.list_ssl_ciphers(cipher_list?, ciphersuites?, protocol?)
返回默认SSL密码作为字符串。cipher_list(TLSv1.3之前)和ciphersuites(TLSv1.3)可用于扩展与protocol匹配的密码设置。
openssl.list_ssl_ciphers()
openssl.list_ssl_ciphers("ECDHE-ECDSA-AES128-SHA")
openssl.list_ssl_ciphers("ECDHE-ECDSA-AES128-SHA", nil, "TLSv1.2")
openssl.list_ssl_ciphers("ECDHE-ECDSA-AES128-SHA", "TLS_CHACHA20_POLY1305_SHA256", "TLSv1.3")
resty.openssl.ctx
提供OSSL_LIB_CTX上下文切换的模块。
OSSL_LIB_CTX是OpenSSL库的内部上下文类型。应用程序可以分配自己的上下文,但也可以使用NULL来使用默认上下文,适用于需要OSSL_LIB_CTX参数的函数。
有关更深入的阅读,请参见OSSL_LIB_CTX.3。
当前有效的上下文模块:
此模块仅在OpenSSL 3.0或更高版本中可用。
ctx.new
语法: ok, err = ctx.new(request_context_only?, conf_file?)
创建一个新上下文,并将其用作此模块的默认上下文。当request_context_only设置为true时,上下文仅在当前请求的上下文中使用。conf_file可以选择性地指定OpenSSL配置文件以创建上下文。
创建的上下文将在其生命周期内自动释放。
-- 从给定提供者实现初始化AES密码实例,仅用于当前请求,而不干扰其他代码部分
-- 或将来请求使用相同算法。
assert(require("resty.openssl.ctx").new(true))
local p = assert(require("resty.openssl.provider").load("myprovider"))
local c = require("resty.openssl.cipher").new("aes256")
print(c:encrypt(string.rep("0", 32), string.rep("0", 16), "🦢"))
-- 不需要释放提供者和ctx,它们会被自动GC
ctx.free
语法: ctx.free(request_context_only?)
释放之前由ctx.new创建的上下文。
resty.openssl.err
提供错误消息的模块。
err.format_error
语法: msg = err.format_error(ctx_msg?, return_code?, all_errors?)
语法: msg = err.format_all_errors(ctx_msg?, return_code?)
返回最后错误代码的最新错误消息。错误格式如下:
[ctx_msg]: code: [return_code]: error:[error code]:[library name]:[func name]:[reason string]:[file name]:[line number]:
在OpenSSL 3.x之前,错误格式如下:
[ctx_msg]: code: [return_code]: [file name]:[line number]:error:[error code]:[library name]:[func name]:[reason string]:
如果all_errors设置为true,将返回所有错误,而不仅仅是最新的错误。此库内部抛出的所有错误仅抛出最新错误。
例如:
local f = io.open("t/fixtures/ec_key_encrypted.pem"):read("*a")
local privkey, err = require("resty.openssl.pkey").new(f, {
format = "PEM",
type = "pr",
passphrase = "wrongpasswrod",
})
ngx.say(err)
-- pkey.new:load_key: error:4800065:PEM routines:PEM_do_header:bad decrypt:crypto/pem/pem_lib.c:467:
err.get_last_error_code
语法: code = err.get_last_error_code()
返回最后错误代码。
err.get_lib_error_string
语法: lib_error_message = err.get_lib_error_string(code?)
返回最后错误代码的库名称作为字符串。如果设置了code,则返回与提供的错误代码对应的库名称。
err.get_reason_error_string
语法: reason_error_message = err.get_reason_error_string(code?)
返回最后错误代码的原因作为字符串。如果设置了code,则返回与提供的错误代码对应的原因。
resty.openssl.version
提供版本信息的模块。
resty.openssl.provider
与提供者交互的模块。此模块仅在OpenSSL >= 3.0.0上工作。
provider.load
语法: pro, err = provider.load(name, try?)
加载名为name的提供者。如果try设置为true,则如果提供者无法加载和初始化,OpenSSL将不会禁用后备提供者。如果提供者成功加载,则禁用后备提供者。
默认情况下,此函数将提供者加载到默认上下文中,这意味着它将影响同一进程中使用默认上下文的其他应用程序。如果不希望这种行为,请考虑使用ctx仅在有限范围内加载提供者。
provider.istype
语法: ok = pkey.provider(table)
如果表是provider的实例,则返回true。否则返回false。
provider.is_available
语法: ok, err = provider.is_available(name)
检查指定的提供者是否可用。
provider.set_default_search_path
语法: ok, err = provider.set_default_search_path(name)
指定用于查找提供者的默认搜索路径。
provider:unload
语法: ok, err = pro:unload(name)
卸载之前由provider.load加载的提供者。
provider:self_test
语法: ok, err = pro:self_test(name)
按需运行提供者的自我测试。如果自我测试失败,则提供者将无法提供任何进一步的服务和算法。
provider:get_params
语法: ok, err = pro:get_params(key1, key2?...)
返回一个或多个提供者参数值。
local pro = require "resty.openssl.provider"
local p = pro.load("default")
local name = assert(p:get_params("name"))
print(name)
-- 输出 "OpenSSL Default Provider"
local result = assert(p:get_params("name", "version", "buildinfo", "status"))
print(require("cjson").encode(result))
-- 输出 '{"buildinfo":"3.0.0-alpha7","name":"OpenSSL Default Provider","status":1,"version":"3.0.0"}'
resty.openssl.pkey
与私钥和公钥(EVP_PKEY)交互的模块。
每种密钥类型可能仅支持部分操作:
| 密钥类型 | 加载现有密钥 | 密钥生成 | 加密/解密 | 签名/验证 | 密钥交换 |
|---|---|---|---|---|---|
| RSA | Y | Y | Y | Y | |
| DH | Y | Y | Y | ||
| EC | Y | Y | Y (ECDSA) | Y (ECDH) | |
| Ed25519 | Y | Y | Y (PureEdDSA) | ||
| X25519 | Y | Y | Y (ECDH) | ||
| Ed448 | Y | Y | Y (PureEdDSA) | ||
| X448 | Y | Y | Y (ECDH) |
对EC和ECX的直接加密和解密支持不存在,但可以通过pkey:derive、kdf和cipher实现类似ECIES的过程。
pkey.new
加载现有密钥
语法: pk, err = pkey.new(string, opts?)
支持加载以PEM、DER或JWK格式传递的私钥或公钥,作为第一个参数string。
第二个参数opts接受一个可选表,以约束密钥加载的行为。
opts.format: 显式设置为"PEM"、"DER"、"JWK"以加载特定格式,或设置为"*"以自动检测opts.type: 显式设置为"pr"表示私钥,"pu"表示公钥;设置为"*"以自动检测
加载PEM编码的RSA密钥时,它可以是PKCS#8编码的SubjectPublicKeyInfo/PrivateKeyInfo或PKCS#1编码的RSAPublicKey/RSAPrivateKey。
加载加密的PEM编码密钥时,解密所需的passphrase可以在opts.passphrase或opts.passphrase_cb中设置:
pkey.new(pem_or_der_text, {
format = "*", -- 选择 "PEM"、"DER"、"JWK" 或 "*" 进行自动检测
type = "*", -- 选择 "pr" 表示私钥,"pu" 表示公钥,"*" 表示自动检测
passphrase = "secret password", -- PEM加密的密码
passphrase_cb = function()
return "secret password"
end, -- PEM加密的密码回调函数
}
加载JWK时,有几个注意事项:
- 确保传入的编码JSON文本必须经过base64解码。
- 当使用OpenSSL 1.1.1或lua-resty-openssl早于1.6.0时,JWK密钥上的type约束仅在OpenSSL 3.x和lua-resty-openssl 1.6.0上受支持。否则,提供的JSON中的参数将决定加载私钥或公钥,指定type将导致错误;此外,对于OKP密钥(x参数),公钥部分不会被尊重,如果指定,则从私钥部分(d参数)派生。
- 仅支持RSA、P-256、P-384和P-512的EC,Ed25519、X25519、Ed448和X448的OKP密钥。
- 签名和验证必须使用ecdsa_use_raw选项以与EC密钥的JWS标准配合使用。有关详细信息,请参见pkey:sign和pkey.verify。
- 在OpenResty外部运行时,需要安装JSON库(cjson或dkjson)和basexx。
密钥生成
语法: pk, err = pkey.new(config?)
生成新的公钥或私钥。
要生成RSA密钥,config表可以具有bits和exp字段以控制密钥生成。
当config被发出时,此函数生成2048位RSA密钥,exponent为65537,相当于:
local key, err = pkey.new({
type = 'RSA',
bits = 2048,
exp = 65537
})
要生成EC或DH密钥,请参考pkey.paramgen以获取config表的可能值。例如:
local key, err = pkey.new({
type = 'EC',
curve = 'prime256v1',
})
也可以将PEM编码的EC或DH参数传递给config.param以生成密钥:
local dhparam = pkey.paramgen({
type = 'DH',
group = 'dh_1024_160'
})
-- 或
-- local dhparam = io.read("dhparams.pem"):read("*a")
local key, err = pkey.new({
type = 'DH',
param = dhparam,
})
还可以在config表中传递原始pkeyopt控制字符串,如在genpkey CLI程序中使用。
有关选项的列表,请参见openssl-genpkey(1)。
例如:
pkey.new({
type = 'RSA',
bits = 2048,
exp = 65537,
})
-- 与
pkey.new({
type = 'RSA',
exp = 65537,
"rsa_keygen_bits:4096",
})
-- 相同
密钥组合
语法: pk, err = pkey.new(config?)
使用现有参数组合公钥或私钥。要查看每个密钥的参数列表,请参考pkey:set_parameters。
仅type和params应存在于config表中,所有其他键将被忽略。
local private_bn = require "resty.openssl.bn".new("7F48282CCA4C1A65D589C06DBE9C42AE50FBFFDF3A18CBB48498E1DE47F11BE1A3486CD8FA950D68F111970F922279D8", 16)
local p_384, err = assert(require("resty.openssl.pkey").new({
type = "EC",
params = {
private = private_bn,
group = "secp384r1",
}
}))
pkey.istype
语法: ok = pkey.istype(table)
如果表是pkey的实例,则返回true。否则返回false。
pkey.paramgen
语法: pem_txt, err = pk.paramgen(config)
为EC或DH密钥生成参数,并以PEM编码文本输出。
对于EC密钥:
| 参数 | 描述 |
|---|---|
| type | "EC" |
| curve | EC曲线。如果省略,默认为"prime192v1"。要查看支持的EC曲线列表,请使用openssl ecparam -list_curves。 |
对于DH密钥:
| 参数 | 描述 |
|---|---|
| type | "DH" |
| bits | 生成一个新的DH参数,位数为bits。如果省略,默认为2048。从OpenSSL 3.0开始,仅允许位数等于2048。 |
| group | 使用预定义组而不是生成新组。如果设置了group,则将忽略bit。 |
group的可能值为:
- RFC7919 "ffdhe2048"、"ffdhe3072"、"ffdhe4096"、"ffdhe6144"、"ffdhe8192"
- RFC5114 "dh_1024_160"、"dh_2048_224"、"dh_2048_256"
- RFC3526 "modp_1536"、"modp_2048"、"modp_3072"、"modp_4096"、"modp_6144"、"modp_8192"
local pem, err = pkey.paramgen({
type = 'EC',
curve = 'prime192v1',
})
local pem, err = pkey.paramgen({
type = 'DH',
group = 'ffdhe4096',
})
还可以在config表中传递原始pkeyopt控制字符串,如在genpkey CLI程序中使用。
有关选项的列表,请参见openssl-genpkey(1)。
pkey:get_provider_name
语法: name = pkey:get_provider_name()
返回pkey的提供者名称。
此函数自OpenSSL 3.0以来可用。
pkey:gettable_params, pkey:settable_params, pkey:get_param, pkey:set_params
查询可设置或可获取的参数,并设置或获取参数。 请参见通用EVP参数获取器/设置器。
pkey:get_parameters
语法: parameters, err = pk:get_parameters()
返回包含pkey实例的parameters的表。
pkey:set_parameters
语法: ok, err = pk:set_parameters(params)
从表params中设置pkey的参数。
如果参数未在params表中设置,则在pkey实例中保持不变。
local pk, err = require("resty.openssl.pkey").new()
local parameters, err = pk:get_parameters()
local e = parameters.e
ngx.say(e:to_number())
-- 输出 65537
local ok, err = pk:set_parameters({
e = require("resty.openssl.bn").from_hex("100001")
})
local ok, err = pk:set_parameters(parameters)
RSA密钥的参数:
| 参数 | 描述 | 类型 |
|---|---|---|
| n | 公钥和私钥的公用模数 | bn |
| e | 公共指数 | bn |
| d | 私有指数 | bn |
| p | n的第一个因子 | bn |
| q | n的第二个因子 | bn |
| dmp1 | d mod (p - 1),指数1 |
bn |
| dmq1 | d mod (q - 1),指数2 |
bn |
| iqmp | (InverseQ)(q) = 1 mod p,系数 |
bn |
EC密钥的参数:
| 参数 | 描述 | 类型 |
|---|---|---|
| private | 私钥 | bn |
| public | 公钥 | bn |
| x | 公钥的x坐标 | bn |
| y | 公钥的y坐标 | bn |
| group | 命名曲线组 | [NID]作为数字,当作为set_parameters()传递时,也可以使用文本表示。与luaossl不同,返回的是EC_GROUP实例。 |
不可能同时设置x、y和public,因为x和y基本上是public的另一种表示形式。此外,目前只能同时设置x和y。
DH密钥的参数:
| 参数 | 描述 | 类型 |
|---|---|---|
| private | 私钥 | bn |
| public | 公钥 | bn |
| p | 素模数 | bn |
| q | 参考位置 | bn |
| g | 基数生成器 | bn |
Curve25519和Curve448密钥的参数:
| 参数 | 描述 | 类型 |
|---|---|---|
| private | 以字节表示的原始私钥 | string |
| public | 以字节表示的原始公钥 | string |
pkey:is_private
语法: ok = pk:is_private()
检查pk是否为私钥。如果是私钥,则返回true;如果是公钥,则返回false。
pkey:get_key_type
语法: obj, err = pk:get_key_type(nid_only?)
返回私钥的密钥类型的ASN1_OBJECT作为表。
从lua-resty-openssl 1.6.0开始,可以设置可选参数nid_only为true,仅返回密钥的数字NID。
local pkey, err = require("resty.openssl.pkey").new({type="X448"})
ngx.say(require("cjson").encode(pkey:get_key_type()))
-- 输出 '{"ln":"X448","nid":1035,"sn":"X448","id":"1.3.101.111"}'
ngx.say(pkey:get_key_type(true))
-- 输出 1035
pkey:get_size
语法: size, err = pk:get_size()
返回几乎所有可以使用pkey执行的操作的输出缓冲区的最大合适大小。
对于RSA密钥,这是模数的大小。 对于EC、Ed25519和Ed448密钥,这是私钥的大小。 对于DH密钥,这是素模数的大小。
pkey:get_default_digest_type
语法: obj, err = pk:get_default_digest_type()
返回私钥的密钥类型的ASN1_OBJECT作为表。如果mandatory为true,则返回的表中还会返回附加字段mandatory,表示其他摘要不能使用。
local pkey, err = require("resty.openssl.pkey").new()
ngx.say(require("cjson").encode(pkey:get_default_digest_type()))
-- 输出 '{"ln":"sha256","nid":672,"id":"2.16.840.1.101.3.4.2.1","mandatory":false,"sn":"SHA256"}'
pkey:sign
语法: signature, err = pk:sign(digest)
语法: signature, err = pk:sign(message, md_alg?, padding?, opts?)
使用在pkey实例中定义的私钥执行摘要签名。第一个参数必须是resty.openssl.digest实例或字符串。如果有任何错误,则返回签名文本。
当将摘要实例作为第一个参数传递时,不应调用final(),用户应仅使用update()。 此模式仅支持RSA和EC密钥。
当将字符串作为第一个参数传递时,md_alg参数将指定在签名时使用的名称。当md_alg未定义时,对于RSA和EC密钥,此函数默认使用SHA256。对于Ed25519或Ed448密钥,此函数执行PureEdDSA签名,不应指定消息摘要,并且不会被使用。
对于RSA密钥,还可以指定padding方案,选择如下:
pkey.PADDINGS = {
RSA_PKCS1_PADDING = 1,
RSA_SSLV23_PADDING = 2,
RSA_NO_PADDING = 3,
RSA_PKCS1_OAEP_PADDING = 4,
RSA_X931_PADDING = 5, -- 仅签名
RSA_PKCS1_PSS_PADDING = 6, -- 仅签名和验证
}
当padding为RSA_PKCS1_PSS_PADDING时,可以通过设置opts.pss_saltlen指定PSS盐长度。
对于EC密钥,此函数执行ECDSA签名。
请注意,OpenSSL不支持使用过时的MD5哈希算法进行EC数字签名(ECDSA),并将在此组合上返回错误。请参见EVP_DigestSign(3)以获取算法和相关公钥算法的列表。通常,ECDSA签名以ASN.1 DER格式编码。如果opts表中包含ecdsa_use_raw字段并且值为true,则返回的二进制仅是pr和ps的二进制表示的连接。这在将签名作为JWS发送时非常有用。
opts是一个表,接受附加参数,选择如下:
{
pss_saltlen, -- 仅对于PSS模式,此选项指定盐长度。
mgf1_md, -- 对于PSS和OAEP填充设置MGF1摘要。如果在PSS模式下未显式设置MGF1摘要,则使用签名摘要。
oaep_md, -- 用于OAEP哈希函数的摘要。如果未显式设置,则使用SHA1。
}
还可以传递原始pkeyopt控制字符串,如在pkeyutl CLI程序中使用。这允许用户传入未显式支持的参数。
请参见openssl-pkeyutl(1)以获取选项列表。
pk:sign(message, nil, pk.PADDINGS.RSA_PKCS1_OAEP_PADDING, {
oaep_md = "sha256",
})
-- 与
pk:sign(message, nil, nil, {
"rsa_padding_mode:oaep",
"rsa_oaep_md:sha256",
})
-- 在pkeyutl CLI中,上述等效于:`openssl pkeyutl -sign -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256
要签署未进行消息摘要的消息,请检查pkey:sign_raw。
pkey:verify
语法: ok, err = pk:verify(signature, digest)
语法: ok, err = pk:verify(signature, message, md_alg?, padding?, opts?)
验证签名(可以是由pkey:sign返回的字符串)。第二个参数必须是使用与sign中使用的相同摘要算法的resty.openssl.digest实例或字符串。如果验证成功,ok返回true,否则返回false。请注意,当验证失败时,使用OpenSSL 1.1.1或更低版本时,err将不会被设置。
当将摘要实例作为第二个参数传递时,不应调用final(),用户应仅使用update()。此模式仅支持RSA和EC密钥。
当将字符串作为第二个参数传递时,md_alg参数将指定在验证时使用的名称。当md_alg未定义时,对于RSA和EC密钥,此函数默认使用SHA256。对于Ed25519或Ed448密钥,此函数执行PureEdDSA验证,不应指定消息摘要,并且不会被使用。
当密钥为RSA密钥时,该函数接受一个可选参数padding,其值选择与pkey:sign中的值相同。当padding为RSA_PKCS1_PSS_PADDING时,可以通过设置opts.pss_saltlen指定PSS盐长度。
对于EC密钥,此函数执行ECDSA验证。通常,ECDSA签名应以ASN.1 DER格式编码。如果opts表中包含ecdsa_use_raw字段并且值为true,则此库将signature视为pr和ps的二进制表示的连接。这在验证签名作为JWS时非常有用。
opts是一个表,接受附加参数,其值选择与pkey:sign中的值相同。
-- RSA和EC密钥
local pk, err = require("resty.openssl.pkey").new()
local digest, err = require("resty.openssl.digest").new("SHA256")
digest:update("dog")
-- 错误:
-- digest:final("dog")
local signature, err = pk:sign(digest)
-- 默认使用SHA256
local signature, err = pk:sign("dog")
ngx.say(ngx.encode_base64(signature))
-- 默认使用SHA256和PSS填充
local signature_pss, err = pk:sign("dog", "sha256", pk.PADDINGS.RSA_PKCS1_PSS_PADDING)
digest, err = require("resty.openssl.digest").new("SHA256")
digest:update("dog")
local ok, err = pk:verify(signature, digest)
-- 默认使用SHA256
local ok, err = pk:verify(signature, "dog")
-- 使用SHA256和PSS填充
local ok, err = pk:verify(signature_pss, "dog", "sha256", pk.PADDINGS.RSA_PKCS1_PSS_PADDING)
-- Ed25519和Ed448密钥
local pk, err = require("resty.openssl.pkey").new({
type = "Ed25519",
})
local signature, err = pk:sign("23333")
ngx.say(ngx.encode_base64(signature))
要验证未进行消息摘要的消息,请检查pkey:verify_raw和pkey:verify_recover。
pkey:encrypt
语法: cipher_txt, err = pk:encrypt(txt, padding?, opts?)
使用pkey实例加密明文txt,必须加载公钥。
可选的第二个参数padding的含义与pkey:sign中的含义相同。
如果省略,padding默认为pkey.PADDINGS.RSA_PKCS1_PADDING。
第三个可选参数opts的含义与pkey:sign中的含义相同。
pkey:decrypt
语法: txt, err = pk:decrypt(cipher_txt, padding?, opts?)
使用pkey实例解密密文cipher_txt,必须加载私钥。
可选的第二个参数padding的含义与pkey:sign中的含义相同。
如果省略,padding默认为pkey.PADDINGS.RSA_PKCS1_PADDING。
第三个可选参数opts的含义与pkey:sign中的含义相同。
local pkey = require("resty.openssl.pkey")
local privkey, err = pkey.new()
local pub_pem = privkey:to_PEM("public")
local pubkey, err = pkey.new(pub_pem)
local s, err = pubkey:encrypt("🦢", pkey.PADDINGS.RSA_PKCS1_PADDING)
ngx.say(#s)
-- 输出 256
local decrypted, err = privkey:decrypt(s)
ngx.say(decrypted)
-- 输出 "🦢"
pkey:sign_raw
语法: signature, err = pk:sign_raw(txt, padding?, opts?)
使用pkey实例对密文cipher_txt进行签名,必须加载私钥。
可选的第二个参数padding的含义与pkey:sign中的含义相同。
如果省略,padding默认为pkey.PADDINGS.RSA_PKCS1_PADDING。
第三个可选参数opts的含义与pkey:sign中的含义相同。
此函数在某些实现中也可以称为“私有加密”,例如NodeJS或PHP。 请注意,正如函数名称所暗示的,这个函数不安全,不能被视为加密。 在开发新应用程序时,用户应使用pkey:sign进行带摘要的签名,或使用pkey:encrypt进行加密。
请查看examples/raw-sign-and-recover.lua以获取示例。
pkey:verify_raw
语法: ok, err = pk:verify_raw(signature, data, md_alg, padding?, opts?)
使用pkey实例验证密文signature与消息data,必须加载公钥。将消息摘要设置为md_alg,但不自动进行消息摘要,换句话说,此函数假定data已经使用md_alg进行了哈希。
当md_alg未定义时,对于RSA和EC密钥,此函数默认使用SHA256。对于Ed25519或Ed448密钥,则未设置默认值。
可选的第四个参数padding的含义与pkey:sign中的含义相同。
如果省略,padding默认为pkey.PADDINGS.RSA_PKCS1_PADDING。
第五个可选参数opts的含义与pkey:sign中的含义相同。
请查看examples/raw-sign-and-recover.lua以获取示例。
pkey:verify_recover
语法: txt, err = pk:verify_recover(signature, padding?, opts?)
使用pkey实例验证密文signature,必须加载公钥,并返回被签名的原始文本。此操作仅支持RSA密钥。
可选的第二个参数padding的含义与pkey:sign中的含义相同。
如果省略,padding默认为pkey.PADDINGS.RSA_PKCS1_PADDING。
第三个可选参数opts的含义与pkey:sign中的含义相同。
此函数在某些实现中也可以称为“公有解密”,例如NodeJS或PHP。
请查看examples/raw-sign-and-recover.lua以获取示例。
pkey:derive
语法: txt, err = pk:derive(peer_key)
从公钥算法共享秘密peer_key派生,必须是一个resty.openssl.pkey实例。
请查看examples/x25519-dh.lua以获取有关X25519密钥与DH算法的密钥交换如何工作的示例。
pkey:tostring
语法: txt, err = pk:tostring(private_or_public?, fmt?, is_pkcs1?)
以PEM格式文本输出pkey实例的私钥或公钥。
第一个参数必须选择public、PublicKey、private、PrivateKey或nil。
第二个参数fmt可以是PEM、DER、JWK或nil。
如果省略这两个参数,则此函数返回公钥的PEM表示。
如果is_pkcs1设置为true,则输出使用PKCS#1 RSAPublicKey结构编码;
当前仅支持以PEM格式的RSA密钥写出PKCS#1编码的RSA密钥。
pkey:to_PEM
语法: pem, err = pk:to_PEM(private_or_public?, is_pkcs1?)
等同于pkey:tostring(private_or_public, "PEM", is_pkcs1)。
resty.openssl.bn
模块以暴露BIGNUM结构。注意bignum是一个大整数,不支持浮点运算(如平方根)。
bn.new
语法: b, err = bn.new(number?)
语法: b, err = bn.new(string?, base?)
创建一个bn实例。第一个参数可以是:
nil以创建一个空的bn实例。- Lua数字以初始化bn实例。
- 字符串以初始化bn实例。第二个参数
base指定字符串的基数,可以取值(与Ruby OpenSSL.BN API兼容): 10或省略,表示十进制字符串("23333")16,表示十六进制编码字符串("5b25")2,表示二进制字符串("\x5b\x25")0,表示MPI格式字符串("\x00\x00\x00\x02\x5b\x25")
MPI是一种格式,由以4字节大端数字表示的数字长度和以大端格式表示的数字组成,其中最高有效位表示负数(最高有效位设置的数字的表示前面加上空字节)。
bn.dup
语法: b, err = bn.dup(bn_ptr_cdata)
复制一个BIGNUM*以创建一个新的bn实例。
bn.istype
语法: ok = bn.istype(table)
如果表是bn的实例,则返回true。否则返回false。
bn.set
语法: b, err = bn:set(number)
语法: b, err = bn:set(string, base?)
重用现有的bn实例,并使用给定的数字或字符串重置其值。 请参考bn.new以获取支持的参数类型。
bn.from_binary, bn:to_binary
语法: bn, err = bn.from_binary(bin)
语法: bin, err = bn:to_binary(padto?)
从二进制字符串创建一个bn实例。
以二进制字符串导出BIGNUM值。
bn:to_binary接受一个可选的数字参数padto,可用于将输出的前导零填充到特定长度。
local to_hex = require "resty.string".to_hex
local b, err = require("resty.openssl.bn").from_binary("\x5b\x25")
local bin, err = b:to_binary()
ngx.say(to_hex(bin))
-- 输出 "5b25"
bn.from_mpi, bn:to_mpi
语法: bn, err = bn.from_mpi(bin)
语法: bin, err = bn:to_mpi()
从MPI格式的二进制字符串创建一个bn实例。
以MPI格式的二进制字符串导出BIGNUM值。
local to_hex = require "resty.string".to_hex
local b, err = require("resty.openssl.bn").from_mpi("\x00\x00\x00\x02\x5b\x25")
local bin, err = b:to_mpi()
ngx.say(to_hex(bin))
-- 输出 "000000025b25"
bn.from_hex, bn:to_hex
语法: bn, err = bn.from_hex(hex)
语法: hex, err = bn:to_hex()
从十六进制编码字符串创建一个bn实例。注意,前导0x不应包含。可以包含指示符号的前导-。
将bn实例导出为十六进制编码字符串。
local bn = require("resty.openssl.bn")
local b = bn.from_hex("5B25")
local hex, err = b:to_hex()
ngx.say(hex)
-- 输出 "5B25"
bn.from_dec, bn:to_dec
语法: bn, err = bn.from_dec(dec)
语法: dec, err = bn:to_dec()
从十进制字符串创建一个bn实例。可以包含指示符号的前导-。
将bn实例导出为十进制字符串。
local bn = require("resty.openssl.bn")
local b = bn.from_dec("23333")
local dec, err = b:to_dec()
ngx.say(dec)
-- 输出 "23333"
bn:to_number
语法: n, err = bn:to_number()
语法: n, err = bn:tonumber()
将bn实例的最低32位或64位部分(基于ABI)导出为数字。这在用户希望执行位运算时非常有用。
local bn = require("resty.openssl.bn")
local b = bn.from_dec("23333")
local n, err = b:to_number()
ngx.say(n)
-- 输出 23333
ngx.say(type(n))
-- 输出 "number"
bn.generate_prime
语法: bn, err = bn.generate_prime(bits, safe)
生成一个位长为bits的伪随机素数。
如果safety为true,则将是安全素数(即素数p,使得(p-1)/2也是素数)。
在调用BN_generate_prime_ex()之前,PRNG必须被播种。 素数生成的错误概率微不足道。
bn:__metamethods
可以像数字一样执行各种数学运算。
local bn = require("resty.openssl.bn")
local a = bn.new(123456)
local b = bn.new(222)
-- 以下返回一个bn
local r
r = -a
r = a + b
r = a - b
r = a * b
r = a / b -- 等于bn:idiv,返回向下取整
r = a % b
-- 所有操作都可以在数字和bignum之间执行
r = a + 222
r = 222 + a
-- 以下返回布尔值
local bool
bool = a < b
bool = a >= b
-- 与数字之间的比较将不起作用
-- 错误:bool = a < 222
bn:add, bn:sub, bn:mul, bn:div, bn:exp, bn:mod, bn:gcd
语法: r = a:op(b)
语法: r = bn.op(a, b)
执行数学运算op。
add: 加法sub: 减法mul: 乘法div,idiv: 向下取整除法(除法向下取整到最接近的整数)exp,pow:a的b次方,此函数比重复a * a * ...更快。mod: 取模gcd:a和b的最大公约数。
注意add、sub、mul、div、mod也可以使用+、-、*、/、%运算符。
请参见上述部分以获取示例。
local bn = require("resty.openssl.bn")
local a = bn.new(123456)
local b = bn.new(9876)
local r
-- 以下相等
r = a:add(b)
r = bn.add(a, b)
r = a:add(9876)
r = bn.add(a, 9876)
r = bn.add(123456, b)
r = bn.add(123456, 9876)
bn:sqr
语法: r = a:sqr()
语法: r = bn.sqr(a)
计算a的2次方。此函数比r = a * a更快。
bn:mod_add, bn:mod_sub, bn:mod_mul, bn:mod_exp
语法: r = a:op(b, m)
语法: r = bn.op(a, b, m)
执行模数学运算op。
mod_add: 将a加到b模mmod_sub: 从a中减去b模mmod_mul: 将a乘以b并找到相对于模m的非负余数mod_exp,mod_pow: 计算a的b次方模m(r=a^b % m)。此函数使用的时间和空间比exp少。不要在m为偶数且任何参数具有BN_FLG_CONSTTIME标志时调用此函数。
local bn = require("resty.openssl.bn")
local a = bn.new(123456)
local b = bn.new(9876)
local r
-- 以下相等
r = a:mod_add(b, 3)
r = bn.mod_add(a, b, 3)
r = a:mod_add(9876, 3)
r = bn.mod_add(a, 9876, 3)
r = bn.mod_add(123456, b, 3)
r = bn.mod_add(123456, 9876, 3)
bn:mod_sqr
语法: r = a:mod_sqr(m)
语法: r = bn.mod_sqr(a, m)
对a进行模m的平方。
bn:lshift, bn:rshift
语法: r = bn:lshift(bit)
语法: r = bn.lshift(a, bit)
语法: r = bn:rshift(bit)
语法: r = bn.rshift(a, bit)
位移a到bit位。
bn:is_zero, bn:is_one, bn:is_odd, bn:is_word
语法: ok = bn:is_zero()
语法: ok = bn:is_one()
语法: ok = bn:is_odd()
语法: ok, err = bn:is_word(n)
检查bn是否为0、1、奇数或数字n。
bn:is_prime
语法: ok, err = bn:is_prime(nchecks?)
检查bn是否为素数。如果是素数,则返回true,错误概率小于0.25^nchecks,并返回任何错误。如果省略,nchecks设置为0,这意味着根据数字的大小选择迭代次数。
此函数执行Miller-Rabin概率素性测试,具有nchecks迭代。如果nchecks == BN_prime_checks (0),则使用的迭代次数使得随机输入的假阳性率最多为2^-64。错误率取决于素数的大小,对于更大的素数会降低。对于308位,率为2^-80,对于852位,率为2^-112,对于1080位,率为2^-128,对于3747位,率为2^-192,对于6394位,率为2^-256。
当素数的来源不是随机或不可信时,检查的次数需要更高,以达到相同的保证水平:它应等于目标安全级别的二分之一(如有必要,四舍五入到下一个整数)。例如,要达到128位安全级别,nchecks应设置为64。
另请参阅BN_is_prime(3)。
resty.openssl.cipher
与对称加密(EVP_CIPHER)交互的模块。
cipher.new
语法: d, err = cipher.new(cipher_name, properties?)
创建一个密码实例。cipher_name是密码算法名称的不区分大小写字符串。
要查看实现的密码算法列表,请使用openssl.list_cipher_algorithms或openssl list -cipher-algorithms。
从OpenSSL 3.0开始,此函数接受一个可选的properties参数,以显式选择提供者以获取算法。
cipher.istype
语法: ok = cipher.istype(table)
如果表是cipher的实例,则返回true。否则返回false。
cipher.set_buffer_size
语法: ok = cipher.set_buffer_size(sz)
调整所有密码实例使用的内部缓冲区大小。默认缓冲区大小为1024字节。
如果您期望一次将大于1024字节的输入文本传递给update()、encrypt()或decrypt(),则将缓冲区设置为大于预期输入大小将通过使更多代码可JIT化来提高性能。
避免在热路径中调用此函数,因为每次调用时都会重新分配缓冲区。
cipher:get_provider_name
语法: name = cipher:get_provider_name()
返回cipher的提供者名称。
此函数自OpenSSL 3.0以来可用。
cipher:gettable_params, cipher:settable_params, cipher:get_param, cipher:set_params
查询可设置或可获取的参数,并设置或获取参数。 请参见通用EVP参数获取器/设置器。
cipher:encrypt
语法: s, err = cipher:encrypt(key, iv?, s, no_padding?, aead_aad?)
使用密钥key和IViv加密文本s。返回以原始二进制字符串形式加密的文本和任何错误。
可选地接受布尔值no_padding,该值告诉密码启用或禁用填充,默认值为false(启用填充)。如果no_padding为true,则s的长度必须是块大小的倍数,否则将发生错误。
使用GCM或CCM模式或chacha20-poly1305密码时,也可以将附加身份验证数据(AAD)作为第五个参数传递。
此函数是cipher:init、cipher:set_aead_aad(如果适用)然后cipher:final的简写。
cipher:decrypt
语法: s, err = cipher:decrypt(key, iv?, s, no_padding?, aead_aad?, aead_tag?)
使用密钥key和IViv解密文本s。返回以原始二进制字符串形式解密的文本和任何错误。
可选地接受布尔值no_padding,该值告诉密码启用或禁用填充,默认值为false(启用填充)。如果no_padding为true,则s的长度必须是块大小的倍数,否则将发生错误;此外,解密文本中的填充将不会被移除。
使用GCM或CCM模式或chacha20-poly1305密码时,也可以将附加身份验证数据(AAD)作为第五个参数传递,认证标签作为第六个参数。
此函数是cipher:init、cipher:set_aead_aad(如果适用)、cipher:set_aead_tag(如果适用)然后cipher:final的简写。
cipher:init
语法: ok, err = cipher:init(key, iv?, opts?)
使用密钥key和IViv初始化密码。可选的第三个参数是一个表,包含:
{
is_encrypt = false,
no_padding = false,
}
如果密码尚未初始化,则需要在cipher:update和cipher:final之前调用此函数。但不需要在cipher:encrypt和cipher:decrypt中。
如果您希望多次重用cipher实例,则需要调用此函数以清除密码的内部状态。简写函数cipher:encrypt和cipher:decrypt已经处理了初始化和重置。
cipher:update
语法: s, err = cipher:update(partial, ...)
使用一个或多个字符串更新密码。如果密码的数据大于块大小以进行刷新,则该函数将返回第一个参数的非空字符串。此函数可用于以流式方式加密或解密连续数据流。
cipher:update_aead_aad
语法: ok, err = cipher:update_aead_aad(aad)
向密码提供AAD数据,此函数可以多次调用。
cipher:get_aead_tag
语法: tag, err = cipher:get_aead_tag(size?)
从密码中获取长度为size的身份验证标签。如果省略,将返回长度为块大小一半的标签。大小不能超过块大小。
此函数只能在加密完成后调用。
cipher:set_aead_tag
语法: ok, err = cipher:set_aead_tag(tag)
用tag设置密码的身份验证标签。
此函数只能在解密开始之前调用。
cipher:final
语法: s, err = cipher:final(partial?)
以原始二进制字符串返回加密或解密的文本,可选地接受一个字符串进行加密或解密。
-- 加密
local c, err = require("resty.openssl.cipher").new("aes256")
c:init(string.rep("0", 32), string.rep("0", 16), {
is_encrypt = true,
})
c:update("🦢")
local cipher, err = c:final()
ngx.say(ngx.encode_base64(cipher))
-- 输出 "vGJRHufPYrbbnYYC0+BnwQ=="
-- 或:
local c, err = require("resty.openssl.cipher").new("aes256")
local cipher, err = c:encrypt(string.rep("0", 32), string.rep("0", 16), "🦢")
ngx.say(ngx.encode_base64(cipher))
-- 输出 "vGJRHufPYrbbnYYC0+BnwQ=="
-- 解密
local encrypted = ngx.decode_base64("vGJRHufPYrbbnYYC0+BnwQ==")
local c, err = require("resty.openssl.cipher").new("aes256")
c:init(string.rep("0", 32), string.rep("0", 16), {
is_encrypt = false,
})
c:update(encrypted)
local cipher, err = c:final()
ngx.say(cipher)
-- 输出 "🦢"
-- 或:
local c, err = require("resty.openssl.cipher").new("aes256")
local cipher, err = c:decrypt(string.rep("0", 32), string.rep("0", 16), encrypted)
ngx.say(cipher)
-- 输出 "🦢"
注意:在某些实现中,如libsodium或Java,AEAD密码在加密密文的末尾附加tag(或MAC)。在这种情况下,用户需要手动切掉tag(通常为16字节)并分别传递密文和tag。
请查看examples/aes-gcm-aead.lua以获取使用带身份验证的AEAD模式的示例。
cipher:derive
语法: key, iv, err = cipher:derive(key, salt?, count?, md?)
从给定材料派生密钥和IV(如果适用),可用于当前密码。此函数主要用于处理已经从相同算法派生的密钥。较新的应用程序应使用更现代的算法,如由kdf.derive提供的PBKDF2。
count是要执行的迭代计数。如果省略,则设置为1。请注意,最近版本的openssl enc CLI工具在设置-iter大于1时会自动使用PBKDF2,而此函数不会。要使用PBKDF2派生密钥,请参考kdf.derive。
md是要使用的消息摘要名称,可以取值md2、md5、sha或sha1。如果省略,则默认值为sha1。
local cipher = require("resty.openssl.cipher").new("aes-128-cfb")
local key, iv, err = cipher:derive("x")
-- 等同于 `openssl enc -aes-128-cfb -pass pass:x -nosalt -P -md sha1`
resty.openssl.digest
与消息摘要(EVP_MD_CTX)交互的模块。
digest.new
语法: d, err = digest.new(digest_name?, properties?)
创建一个摘要实例。digest_name是摘要算法名称的不区分大小写字符串。
要查看实现的摘要算法列表,请使用openssl.list_digest_algorithms或openssl list -digest-algorithms。
如果省略digest_name,则默认为sha1。特别地,摘要名称"null"表示“空”消息摘要,不执行任何操作:即返回的哈希长度为零。
从OpenSSL 3.0开始,此函数接受一个可选的properties参数,以显式选择提供者以获取算法。
digest.istype
语法: ok = digest.istype(table)
如果表是digest的实例,则返回true。否则返回false。
digest:get_provider_name
语法: name = digest:get_provider_name()
返回digest的提供者名称。
此函数自OpenSSL 3.0以来可用。
digest:gettable_params, digest:settable_params, digest:get_param, digest:set_params
查询可设置或可获取的参数,并设置或获取参数。 请参见通用EVP参数获取器/设置器。
digest:update
语法: ok, err = digest:update(partial, ...)
使用一个或多个字符串更新摘要。
digest:final
语法: str, err = digest:final(partial?)
以原始二进制字符串返回摘要,可选地接受一个字符串进行摘要。
local d, err = require("resty.openssl.digest").new("sha256")
d:update("🦢")
local digest, err = d:final()
ngx.say(ngx.encode_base64(digest))
-- 输出 "tWW/2P/uOa/yIV1gRJySJLsHq1xwg0E1RWCvEUDlla0="
-- 或:
local d, err = require("resty.openssl.digest").new("sha256")
local digest, err = d:final("🦢")
ngx.say(ngx.encode_base64(digest))
-- 输出 "tWW/2P/uOa/yIV1gRJySJLsHq1xwg0E1RWCvEUDlla0="
digest:reset
语法: ok, err = digest:reset()
重置digest实例的内部状态,就像它刚刚通过digest.new创建的一样。
它在内部调用EVP_DigestInit_ex。
用户必须在重用相同的digest实例之前调用此函数。
resty.openssl.hmac
与基于哈希的消息认证码(HMAC_CTX)交互的模块。
自OpenSSL 3.0以来,此模块已弃用,请改用resty.openssl.mac。
hmac.new
语法: h, err = hmac.new(key, digest_name?)
创建一个hmac实例。digest_name是摘要算法名称的不区分大小写字符串。
要查看实现的摘要算法列表,请使用openssl.list_digest_algorithms或openssl list -digest-algorithms。
如果省略digest_name,则默认为sha1。
hmac.istype
语法: ok = hmac.istype(table)
如果表是hmac的实例,则返回true。否则返回false。
hmac:update
语法: ok, err = hmac:update(partial, ...)
使用一个或多个字符串更新HMAC。
hmac:final
语法: str, err = hmac:final(partial?)
以原始二进制字符串返回HMAC,可选地接受一个字符串进行摘要。
local d, err = require("resty.openssl.hmac").new("goose", "sha256")
d:update("🦢")
local hmac, err = d:final()
ngx.say(ngx.encode_base64(hmac))
-- 输出 "k2UcrRp25tj1Spff89mJF3fAVQ0lodq/tJT53EYXp0c="
-- 或:
local d, err = require("resty.openssl.hmac").new("goose", "sha256")
local hmac, err = d:final("🦢")
ngx.say(ngx.encode_base64(hmac))
-- 输出 "k2UcrRp25tj1Spff89mJF3fAVQ0lodq/tJT53EYXp0c="
hmac:reset
语法: ok, err = hmac:reset()
重置hmac实例的内部状态,就像它刚刚通过hmac.new创建的一样。
它在内部调用HMAC_Init_ex。
用户必须在重用相同的hmac实例之前调用此函数。
resty.openssl.mac
与消息认证码(EVP_MAC)交互的模块。
mac.new
语法: h, err = mac.new(key, mac, cipher?, digest?, properties?)
创建一个mac实例。mac是MAC算法名称的不区分大小写字符串。
要查看实现的摘要算法列表,请使用openssl.list_digest_algorithms或`openssl list -digest