跳转至

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分支中找到。

构建状态 luarocks opm

描述

lua-resty-openssl是一个基于FFI的OpenSSL绑定库,目前支持OpenSSL 3.x1.1.1系列。

概述

该库受到luaossl的极大启发,同时使用更接近原始OpenSSL API的命名约定。例如,在OpenSSL C API中调用的函数X509_set_pubkey将期望存在为resty.openssl.x509:set_pubkeyCamelCase被替换为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开始,providermac 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:derivekdfcipher实现类似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.passphraseopts.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参数)派生。 - 仅支持RSAP-256P-384P-512ECEd25519X25519Ed448X448OKP密钥。 - 签名和验证必须使用ecdsa_use_raw选项以与EC密钥的JWS标准配合使用。有关详细信息,请参见pkey:signpkey.verify。 - 在OpenResty外部运行时,需要安装JSON库(cjsondkjson)和basexx

密钥生成

语法: pk, err = pkey.new(config?)

生成新的公钥或私钥。

要生成RSA密钥,config表可以具有bitsexp字段以控制密钥生成。 当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

typeparams应存在于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实例。

不可能同时设置xypublic,因为xy基本上是public的另一种表示形式。此外,目前只能同时设置xy

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, -- 仅签名和验证
  }

paddingRSA_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,则返回的二进制仅是prps的二进制表示的连接。这在将签名作为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中的值相同。当paddingRSA_PKCS1_PSS_PADDING时,可以通过设置opts.pss_saltlen指定PSS盐长度。

对于EC密钥,此函数执行ECDSA验证。通常,ECDSA签名应以ASN.1 DER格式编码。如果opts表中包含ecdsa_use_raw字段并且值为true,则此库将signature视为prps的二进制表示的连接。这在验证签名作为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_rawpkey: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实例的私钥或公钥。 第一个参数必须选择publicPublicKeyprivatePrivateKey或nil。

第二个参数fmt可以是PEMDERJWK或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: ab次方,此函数比重复a * a * ...更快。
  • mod: 取模
  • gcd: ab的最大公约数。

注意addsubmuldivmod也可以使用+、-、*、/、%运算符。 请参见上述部分以获取示例。

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加到bm
  • mod_sub: 从a中减去bm
  • mod_mul: 将a乘以b并找到相对于模m的非负余数
  • mod_exp, mod_pow: 计算ab次方模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)

位移abit位。

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是否为01、奇数或数字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_algorithmsopenssl 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_paddingtrue,则s的长度必须是块大小的倍数,否则将发生错误。

使用GCM或CCM模式或chacha20-poly1305密码时,也可以将附加身份验证数据(AAD)作为第五个参数传递。

此函数是cipher:initcipher: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_paddingtrue,则s的长度必须是块大小的倍数,否则将发生错误;此外,解密文本中的填充将不会被移除。

使用GCM或CCM模式或chacha20-poly1305密码时,也可以将附加身份验证数据(AAD)作为第五个参数传递,认证标签作为第六个参数。

此函数是cipher:initcipher: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:updatecipher:final之前调用此函数。但不需要在cipher:encryptcipher:decrypt中。

如果您希望多次重用cipher实例,则需要调用此函数以清除密码的内部状态。简写函数cipher:encryptcipher: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是要使用的消息摘要名称,可以取值md2md5shasha1。如果省略,则默认值为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_algorithmsopenssl 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_algorithmsopenssl 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