6  新旧 API

6 新旧 API

本章介绍加密和解密的新 API。

CRYPTO 应用程序在其生命周期中不断发展。由于 OpenSSL cryptolib 的 API 也多次更改,CRYPTO 应用程序中的一些部分在内部使用的是非常旧的 API,而另一些部分则使用的是最新的 API。例如,密码名称的内部定义很难维护。

事实证明,以新的方式使用旧 API(稍后将详细介绍),同时保持向后兼容是不可能的。特别是,由于需要在错误消息中提供更多精度,因此无法将其与旧标准结合使用。

因此,旧 API(见下一节)目前保留,但内部使用新的原语实现。

旧函数(从 23.0 开始弃用,从 OTP 24.0 开始删除)用于密码

  • block_encrypt/3
  • block_encrypt/4
  • block_decrypt/3
  • block_decrypt/4
  • stream_init/2
  • stream_init/3
  • stream_encrypt/2
  • stream_decrypt/2
  • next_iv/2
  • next_iv/3

用于支持的算法列表

  • supports/0

以及用于 MAC(消息认证码)

  • cmac/3
  • cmac/4
  • hmac/3
  • hmac/4
  • hmac_init/2
  • hmac_update/2
  • hmac_final/1
  • hmac_final_n/2
  • poly1305/2

用于加密或解密单个二进制文件的新的函数是

在这些函数中,内部加密状态首先创建并使用密码类型、密钥以及可能的其他数据进行初始化。然后加密或解密单个二进制文件,释放加密状态,并返回加密操作的结果。

crypto_one_time_aead 函数用于模式为 ccmgcm 的密码,以及密码 chacha20-poly1305

对于重复加密或解密文本(文本被分成多个部分),内部加密状态仅初始化一次,然后使用相同的状态加密或解密多个二进制文件,函数是

crypto_init 初始化内部密码状态,一个或多个 crypto_update 调用执行实际的加密或解密。请注意,由于 AEAD 密码的性质,无法以这种方式处理它们。

对于重复加密或解密文本(文本被分成多个部分),使用相同的密码和密钥,但每个部分都应应用一个新的初始化向量(nonce),函数是

这些函数的应用场景示例是处理 TLS 协议。

如果未启用填充,则可以省略对 crypto_final/1 的调用。

有关可用算法的信息,请使用

next_iv/2next_iv/3 不需要,因为 crypto_initcrypto_update 包含此功能。

用于计算单个文本段的 MAC 的新函数是

对于计算文本(文本被分成多个部分)的 MAC,请使用

函数 crypto_init/4crypto_update/2 用于加密或解密一系列块。首先, crypto_init/4 的一个调用初始化加密上下文。一个或多个 crypto_update/2 调用对每个块执行实际的加密或解密。

此示例首先显示了对两个块的加密,然后是对密文的解密,但将其分成三个块,只是为了说明对于某些密码,可以对明文和密文进行不同的划分。

	1> crypto:start().
	ok
	2> Key = <<1:128>>.
	<<0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1>>
	3> IV = <<0:128>>.
	<<0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0>>
	4> StateEnc = crypto:crypto_init(aes_128_ctr, Key, IV, true). % encrypt -> true
	#Ref<0.3768901617.1128660993.124047>
	5> crypto:crypto_update(StateEnc, <<"First bytes">>).
	<<67,44,216,166,25,130,203,5,66,6,162>>
	6> crypto:crypto_update(StateEnc, <<"Second bytes">>).
	<<16,79,94,115,234,197,94,253,16,144,151,41>>
	7>
	7> StateDec = crypto:crypto_init(aes_128_ctr, Key, IV, false). % decrypt -> false
	#Ref<0.3768901617.1128660994.124255>
	8> crypto:crypto_update(StateDec, <<67,44,216,166,25,130,203>>).
	<<"First b">>
	9> crypto:crypto_update(StateDec, <<5,66,6,162,16,79,94,115,234,197,
        94,253,16,144,151>>).
	<<"ytesSecond byte">>
	10> crypto:crypto_update(StateDec, <<41>>).
	<<"s">>
	11>

请注意, StateEncStateDec 引用的内部数据会受到对 crypto_update/2 的调用的破坏性更新。这样做是为了在调用与 cryptolib 交互的 nif 时节省时间。在循环中,如果状态保存在循环状态中,则每次加密操作也会节省一次循环状态更新。

例如,一个简单的服务器接收要加密的文本部分,并将结果发送回发送者(Requester

	encode(Crypto, Key, IV) ->
	crypto_loop(crypto:crypto_init(Crypto, Key, IV, true)).

	crypto_loop(State) ->
	receive
        {Text, Requester} ->
        Requester ! crypto:crypto_update(State, Text),
	loop(State)
	end.

上一节 相同的示例,但现在使用 crypto_one_time/5 调用一次。

	1> Key = <<1:128>>.
	<<0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1>>
	2> IV = <<0:128>>.
	<<0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0>>
	3> Txt = [<<"First bytes">>,<<"Second bytes">>].
	[<<"First bytes">>,<<"Second bytes">>]
	4> crypto:crypto_one_time(aes_128_ctr, Key, IV, Txt, true).
	<<67,44,216,166,25,130,203,5,66,6,162,16,79,94,115,234,
	197,94,253,16,144,151,41>>
	5>

[<<"First bytes">>,<<"Second bytes">>] 当然可以是一个二进制文件: <<"First bytesSecond bytes">>

上一节 相同的示例,但现在使用 crypto_one_time_aead/6 调用一次。

	1> Key = <<1:128>>.
	<<0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1>>
	2> IV = <<0:128>>.
	<<0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0>>
	3> Txt = [<<"First bytes">>,<<"Second bytes">>].
	[<<"First bytes">>,<<"Second bytes">>]
	4> AAD = <<"Some bytes">>.
	<<"Some bytes">>
	5> crypto:crypto_one_time_aead(aes_128_gcm, Key, IV, Txt, AAD, true).
	{<<240,130,38,96,130,241,189,52,3,190,179,213,132,1,72,
	192,103,176,90,104,15,71,158>>,
	<<131,47,45,91,142,85,9,244,21,141,214,71,31,135,2,155>>}
	6>

[<<"First bytes">>,<<"Second bytes">>] 当然可以是一个二进制文件: <<"First bytesSecond bytes">>

	1> Key = <<1:128>>.           
	<<0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1>>
	2> StateMac = crypto:mac_init(cmac, aes_128_cbc, Key).                   
	#Ref<0.2424664121.2781478916.232610>
	3> crypto:mac_update(StateMac, <<"First bytes">>).
	#Ref<0.2424664121.2781478916.232610>
	4> crypto:mac_update(StateMac, " ").              
	#Ref<0.2424664121.2781478916.232610>
	5> crypto:mac_update(StateMac, <<"last bytes">>). 
	#Ref<0.2424664121.2781478916.232610>
	6> crypto:mac_final(StateMac).
	<<68,191,219,128,84,77,11,193,197,238,107,6,214,141,160,
	249>>
	7>

并比较结果与仅针对此示例的单个计算。

	7> crypto:mac(cmac, aes_128_cbc, Key, "First bytes last bytes").
	<<68,191,219,128,84,77,11,193,197,238,107,6,214,141,160,
	249>>
	8> v(7) == v(6).
	true
	9> 

此表列出了第一列中已弃用的密码名称,并在第二列中建议用它们替换的名称。

新的名称遵循 OpenSSL libcrypto 名称。格式为 ALGORITM_KEYSIZE_MODE。

算法示例包括 aes、chacha20 和 des。密钥大小是比特数,模式示例包括 cbc、ctr 和 gcm。模式后可能跟着一个数字,具体取决于模式。例如,ccm 模式有一个称为 ccm8 的变体,其中所谓的标签长度为 8 位。

旧名称随着时间的推移失去了任何常见的命名约定,而新名称现在引入了这种约定。新名称包含密钥长度,这提高了密码应用程序较低级别的错误检查。

不要使用 使用
aes_cbc128 aes_128_cbc
aes_cbc256 aes_256_cbc
aes_cbc aes_128_cbc, aes_192_cbc, aes_256_cbc
aes_ccm aes_128_ccm, aes_192_ccm, aes_256_ccm
aes_cfb128 aes_128_cfb128, aes_192_cfb128, aes_256_cfb128
aes_cfb8 aes_128_cfb8, aes_192_cfb8, aes_256_cfb8
aes_ctr aes_128_ctr, aes_192_ctr, aes_256_ctr
aes_gcm aes_128_gcm, aes_192_gcm, aes_256_gcm
des3_cbc des_ede3_cbc
des3_cbf des_ede3_cfb
des3_cfb des_ede3_cfb
des_ede3 des_ede3_cbc
des_ede3_cbf des_ede3_cfb

表 6.1: