SDF引擎

编译与安装

在默认条件下,GmSSL以shared方式编译,libcryptolibssl还有所有的ENGINE都会以动态库编译,ENGINE的动态库在安装的时候被拷贝到默认的目录,命令行程序会从默认的地址寻找这些ENGINE的动态库。

当以no-shared方式编译时,libcryptolibssl编译为静态库,所有的ENGINE都被直接编译到libcrypto中。

  1. 如果要将ENGINE整个编译进libcrypto中,需要 no-dynamic-engine
  2. 如果要整个放在gmssl中,还需要加入no-shared
  3. 如果不希望编译ENGINE,那么no-engine会怎么样?
$ gmssl engine sdf -vvvv
Using configuration from /usr/local/ssl/openssl.cnf
(sdf) GmSSL SDF engine
     SO_PATH: Specifies the path to the vendor's 'sdf' shared library
          (input flags): STRING
     VENDOR: Specifies the library vendor's name
          (input flags): STRING
     OPEN_DEV: Open default device
          (input flags): NO_INPUT
     CLOSE_DEV: Close the current opened device
          (input flags): NO_INPUT
     OPEN_CONTAINER: Open container
          (input flags): NUMERIC
     CLOSE_CONTAINER: Close container
          (input flags): NUMERIC
     IMPORT_FILE: Import file into device
          (input flags): STRING
     EXPORT_FILE: Export file from device
          (input flags): STRING
     DELETE_FILE: Delete file in device
          (input flags): STRING
     SET_EC_ENC_KEY_INDEX: Set ECC encryption key index
          (input flags): NUMERIC
     SET_RSA_ENC_KEY_INDEX: Set RSA encryption key index
          (input flags): NUMERIC

不同型号SDF设备支持的密码算法通常不一样,通过engine命令的-c选项可以查看当前设备支持的密码算法。由于设备支持的算法信息必须从打开的设备中获取,因此默认我们是看不到支持算法的完整列表的。

$ gmssl engine sdf -c
Using configuration from /usr/local/ssl/openssl.cnf
(sdf) GmSSL SDF engine
[Success]: SO_PATH:./libsdf.so
 [RSA, EC, RAND, id-ecPublicKey]

可以通过SDF引擎的扩展命令SO_PATH指定厂商SDF库并通过扩展命令OPEN_DEV打开默认设备,从而可以显示当前设备支持算法的完整列表。

$ gmssl engine sdf -pre SO_PATH:./libsdf.so -pre OPEN_DEV -c
Using configuration from /usr/local/ssl/openssl.cnf
(sdf) GmSSL SDF engine
[Success]: SO_PATH:./libsdf.so
  [RSA, EC, RAND, SM1-ECB, SM1-CBC, SM1-CFB1, SM1-CFB8, SM1-CFB, SM1-OFB, SMS4-ECB, SMS4-CBC, SMS4-CFB1, SMS4-CFB8, SMS4-CFB, SMS4-OFB, SM3, SHA1, SHA256, id-ecPublicKey]

通过OPEN_DEVDEV_INFO可以打开设备并进一步显示设备的详细信息,包括设备厂商、名称、序列号及硬件、固件版本等。

$ sudo gmssl engine sdf -pre SO_PATH:./libsdf.so -pre OPEN_DEV -pre DEV_INFO
Using configuration from /usr/local/ssl/openssl.cnf
(sdf) GmSSL SDF engine
[Success]: SO_PATH:./libsdf.so
[Success]: OPEN_DEV
  Issuer            : SANSEC
  Device Name       : SC26B-1RQHEN
  Serial Number     : 201608032603163
  Hardware Version  : 16842752
  Standard Version  : 1
  Public Key Algors : rsa, rsaEncryption, sm2sign, sm2exchange, sm2encrypt
  Ciphers           : sm1-ecb, sm1-cbc, cbcmac-sm1, sms4-ecb, sms4-cbc, cbcmac-sms4, zuc_128eea3, zuc_128eia3
  Digests           : sm3, sha1, sha256
[Success]: DEV_INFO

生成随机数

随机数生成器是密码硬件非常重要的功能之一。服务器和嵌入式网络设备在启动的初期特别容易由于系统还未产生足够的随机性而产生严重的安全性问题,在部署了密码设备后,可以从密码设备内置的物理随机数发生器中读取随机数,用于初始化操作系统的随机数生成器(如/dev/random)或者GmSSL库内置的随机数生成器。

$ sudo gmssl rand -engine sdf -hex 32
engine "sdf" set.
Using configuration from /usr/local/ssl/openssl.cnf
2838469d01a33c8ff3b52c239d45a8480efa6f9be46723ab974db109caca0ed1

这里我们不建议完全采用密码设备的随机数生成器来生成重要的秘密,如长期使用的私钥。

对称加密

命令行工具的enc命令可以用于做对称加解密,通过-engine sdf选项可以加载SDF引擎并调用SDF设备进行加解密计算。

$ echo -n "secret" | sudo gmssl enc -sms4 -engine sdf -K 12345678123456781234567812345678 -iv 12345678123456781234567812345678 -a
engine "sdf" set.
nRfwzTjn19wynRwMIwi7dg==

杂凑函数

GmSSL命令行工具的dgstsm3命令可以用于计算SM3杂凑值,通过-engine sdf选项可以加载SDF引擎。在默认情况下命令行工具通过SDF引擎访问设备中的密钥,如签名私钥,并不通过设备完成对称加密和杂凑这类需要传输大量数据的计算。但是通过指定-engine_impl选项,可以强行要求命令行调用SDF设备完成密码计算。

$ echo -n abc | sudo gmssl dgst -sm3 -engine sdf -engine_impl
engine "sdf" set.
Using configuration from /usr/local/ssl/openssl.cnf
(stdin)= 66c7f0f462eeedd9d1f2d46bdc10e4e24167c4875cf2f7a2297da02b8f4ba8e0

比较遗憾的是speed命令不能通过引擎调用SM3的硬件实现,但是我们仍然可以用dgst命令测试一下硬件设备的性能。

部分供应商的SDF实现并没有调用硬件实现杂凑算法,而是在SDF动态库中以软件的方式实现。即使厂商的SDF库以硬件实现杂凑算法,考虑到通常CPU更快以及传输数据的延迟,通过SDF引擎调用杂凑算法不合算。这个功能可以通过厂商的实现来验证杂凑算法实现正确性测试。

密钥容器初始化

相对于SKF、PKCS #11等密码设备接口来说,SDF接口并不是一个功能完备的接口,这主要体现在应用程序不能通过SDF接口完成设备初始化、密钥容器初始化、生成私钥、导入私钥和口令设置等必要的初始化操作功能,应用也无法通过SDF接口枚举设备内的密钥容器、密钥、文件等存储对象。

其中不包含密钥管理有关的接口,也无法枚举系统中的密钥容器。密钥容器、密钥、口令等初始化的工作都是通过三未信安专有的命令行管理工具实现的。管理工具可以看到三未信安的密码卡的存储空间支持每种密钥100个。

$ sudo gmssl engine sdf -pre SO_PATH:./libsdf.so -pre VENDOR:sansec -pre OPEN_DEV -pre OPEN_CONTAINER:1
Using configuration from /usr/local/ssl/openssl.cnf
(sdf) GmSSL SDF engine
[Success]: SO_PATH:./libsdf.so
[Success]: VENDOR:sansec
 ...
[Success]: OPEN_DEV
Password for private key 1 > ********
[Success]: OPEN_CONTAINER:1

这里我们打开一个密钥容器。在这个密码卡中支持100个密钥容器,序号从1到100。其中容器1已经初始化为SM2容器,并且已经生成签名密钥对和加解密密钥对。

打开密钥容器

在打开密钥容器的时候命令行提示符会要求管理员输入一个口令,这个口令是该密钥容器的口令。SDF中每个密钥容器的口令可以不同。

下面的例子显示了访问三未信安PCI-E密码卡的例子,其中libsdf.so是指向三未信安动态库的一个软链接。

$ sudo gmssl engine sdf -pre SO_PATH:./libsdf.so -pre VENDOR:sansec -pre OPEN_DEV -pre OPEN_CONTAINER:1
Using configuration from /usr/local/ssl/openssl.cnf
(sdf) GmSSL SDF engine
[Success]: SO_PATH:./libsdf.so
[Success]: OPEN_DEV
[Success]: OPEN_CONTAINER:1

签名与验签

可以通过pkeyutl -sign命令对杂凑值签名。这里我们通过-keyform engine指定签名私钥来自SDF设备。当私钥来自SDF设备时,-inkey所指定的密钥名称就不再被视为文件路径,而是SDF设备内部密钥容器的索引。这里-inkey的参数必须为<type>_<num>.<usage>,其中<type>为公钥算法类型,必须为eccrsa<num>是密钥容器的索引,通常是1开始的整数,<usage>为该密钥容器内密钥的用途,必须为signexch,表示签名密钥或密钥交换/加解密密钥。

echo -n "abc" | gmssl sm3 -binary | \
  sudo gmssl pkeyutl -sign -pkeyopt ec_scheme:sm2 -engine sdf -keyform engine -inkey ecc_1.sign -out sm2.sig
engine "sdf" set.
Password > ********

注意,pkeyutl -sign仅适用于对杂凑值(二进制)进行签名,而不应该用于对消息的签名,因此上面的例子中用gmssl sm3 -binary来生成消息的杂凑值。

验证过程和签名过程类似。

echo -n "abc" | gmssl sm3 -binary | \
  sudo gmssl pkeyutl -verify -pkeyopt ec_scheme:sm2 -engine sdf -keyform engine -inkey ecc_1.sign -sigfile sm2.sig
engine "sdf" set.
Password > ********
Signature Verified Successfully

由于验证签名的过程仅需要公钥,因此我们还可以将签名公钥从SDF设备中导出,用GmSSL的内置的SM2算法实现做签名验证。

gmssl pkey -engine sdf -config sdf.cnf -inform engine -in ecc_1.sign -pubout -out sm2vkey.pem
echo "abc" | gmssl sm3 -binary | gmssl pkeyutl -verify -pkeyopt ec_scheme:sm2 -pubin -inkey sm2vkey.pem -sigfile sm2.sig

SM2加解密

GM/T 0018-2012 密码设备应用接口规范 中的SDF接口仅支持采用外部输入的公钥进行加密,不支持引用内部密钥容器中的公钥进行加密。

对于SDF接口的具体实现来说,加密算法可以在密码卡设备内部实现,也可以仅在SDF动态库中实现。部分厂商对SDF接口做了扩展,支持引用密钥容器进行加密。GmSSL SDF引擎支持这一扩展,因此在通过SDF引擎加密的时候,即可以指定一个公钥文件,也可以指定密钥容器做加密。

echo "Secret" | \
  gmssl pkeyutl -encrypt -engine sdf -keyform engine -inkey ecc_1.exch -out sm2ciphertext.der

这里我们用SDF加密一个短消息。

在SDF设备初始化过程中我们创建了一个SM2密钥容器,现在我们导出其中的加密公钥。

sudo gmssl pkey -engine sdf -config sdf.cnf -inform engine -in ecc_1.exch -pubout -out sm2enckey.pem

我们也可以将公钥从SDF设备中导出并用GmSSL内置的SM2软件实现来加密。

sudo gmssl pkey -engine sdf -inform engine -in ecc_1.exch -pubout -out sm2enckey.pem
echo "secret" | \
  gmssl pkeyutl -encrypt -pkeyopt ec_scheme:sm2 -pkeyopt ec_encrypt_param:sm3 -pubin -inkey ecc_1.exch -out sm2ciphertext.der

注意这个例子中的加密和上面有所不同,我们指定了-pkeyopt ec_scheme:sm2 -pkeyopt ec_encrypt_param:sm3这两个附加的参数。这是因为GmSSL内置支持两种椭圆曲线算法,SECG的ECIES算法和国密的SM2加密算法,因此必须通过-pkeyopt ec_scheme:sm2指定到底用哪个算法。同时GmSSL也支持可选的SM2加密中的杂凑算法(虽然通常采用SM3),因此这里我们通过-pkeyopt ec_encrypt_param:sm3指定这个算法。

echo "Secret" | \
  gmssl pkeyutl -encrypt -pkeyopt ec_scheme:sm2 -engine sdf -config sdf.cnf -inkey ./sm2enc.pem -out sm2ciphertext.der

这里通过-engine sdf来指定采用SDF引擎计算,通过-inkey ./sm2enc.pem指定文件系统中的一个SM2公钥文件。

尽管供应商实现中包括了采用外部私钥进行解密的操作,但是在采用SDF设备的计算环境中以这种方式解密是没有意义的,因此在GmSSL SDF引擎不支持这一功能。

在解密的过程中,仅支持通过内部密钥容器进行解密。

gmssl pkeyutl -decrypt -pkeyopt ec_scheme:sm2 -in sm2ciphertext.der \
      -engine sdf -config sdf.cnf -keyform engine -inkey ecc_1.exch

导入导出证书

和用于USB-Key、智能卡等小设备的SKF接口不同,SDF主要面向PCI-E密码卡,主要的部署环境为Web服务器或服务器密码机。这些应用场景中的证书通常静态部署在文件系统中,因此不需要从设备中读取证书。因此,SDF接口也没有提供原生的证书导入导出功能,只是提供了一组文件读写的接口。

需要注意的是,即使这些文件保存在SDF设备内部,访问这些文件也没有经过任何格外的保护,只要有密码卡的访问权限就可以访问这些数据,因此这些文件是有可能被窃取、篡改和删除的。而密钥容器有格外的口令提供保护。

gmssl engine sdf -pre SO_PATH:$SO_PATH -pre IMPORT_FILE:localhost-signcer.pem

SDF配置文件

engine命令支持通过-pre来指定SDF引擎的扩展命令,从而提供一些必要的选项。但是在encdgstpkey等命令中并不支持-pre调用扩展命令,这时我们可以通过配置文件来这些这些扩展命令。一个典型的SDF配置文件如下面的例子所示。其中比较重要的是SO_PATHOPEN_DEV

# conf file for gmssl sdf engine
openssl_conf = openssl_init

[openssl_init]
engines = engine_section

[engine_section]
sdf = sdf_section

[sdf_section]
engine_id = sdf
SO_PATH = ./libswsds.so
VENDOR = sansec
OPEN_DEV = 
init = 1

TODO

  1. SDF Engine目前不支持SM1、ZUC等算法
  2. speed不支持对Engine的性能测试
  3. 配置文件和默认设置存在冲突
  4. 口令输入的问题,降低输入次数?支持其他输入方式?以及API的支持。
  5. 将标准兼容的代码转移到Engine代码中
  6. 标准兼容代码支持非标准的椭圆曲线密钥长度