OpenResty AES 函数密钥生成原理

最近遇到一个场景,需要在 OpenResty 里使用 AES 对数据进行加密。但是看了一下官方提供的 AES 库,如果数据密码的话,并没有返回实际使用的 key 和 IV,因此查看了一下源码,研究了一下 key 生成的方式,方便其他语言使用。

密钥生成逻辑的核心代码在这:

function _M.new(self, key, salt, _cipher, _hash, hash_rounds, iv_len, enable_padding)

...

    local _hash = _hash or hash.md5
    local hash_rounds = hash_rounds or 1

...

        if C.EVP_BytesToKey(_cipher.method, _hash, salt, key, #key,
                            hash_rounds, gen_key, gen_iv)
            ~= _cipherLength
        then
            return nil, "failed to generate key and iv"
        end
end

其中最核心的部分就是使用了 EVP_BytesToKey 这个函数,进行密钥扩展。这个函数是 OpenSSL 库提供的函数,相关说明在这:

https://www.openssl.org/docs/man3.1/man3/EVP_BytesToKey.html

注意!该函数并不安全!一般不建议在生产中使用该函数!应当使用安全的密钥衍生函数(KDF)!

阅读文档,该函数的主要做法就是对提供的密码进行 hash,如果长度不够 key 或 iv 的要求,则继续进行 hash,直到长足足够为止。

第一轮 hash 直接拼接给定的密码和 salt,从第二轮开始,则将上一轮的结果附加在密码和 salt 前,然后再次进行 hash 操作。注意这里从第二轮开始附加的上一轮结果是二进制结果,如果希望手动使用工具计算的话,需要将 hex 编码或 base64 编码后的结果先进行解码,再附加在输入前。

用 Go 模拟这个过程的代码如下:

package main

import (
    "crypto/sha256"
    "fmt"
)

// EVPBytesToKey 模拟 OpenSSL EVP_BytesToKey 方法
func EVPBytesToKey(password, salt []byte, keyLen, ivLen int) (key, iv []byte) {
    var (
        concatenatedHashes []byte
        previousHash       []byte
        currentHash        []byte
        hash               = sha256.New()
    )

    for len(concatenatedHashes) < keyLen+ivLen {
        hash.Reset()

        // 如果不是第一轮,将上一轮的哈希值加到当前哈希的输入中
        if previousHash != nil {
            hash.Write(previousHash)
        }

        // 加入密码和盐值
        hash.Write(password)
        hash.Write(salt)

        // 当前哈希
        currentHash = hash.Sum(nil)

        // 添加到累积哈希中
        concatenatedHashes = append(concatenatedHashes, currentHash...)

        // 为下一轮准备
        previousHash = currentHash
    }

    // 提取密钥和 IV
    key = concatenatedHashes[:keyLen]
    iv = concatenatedHashes[keyLen : keyLen+ivLen]
    return key, iv
}

func main() {
    password := []byte("password")
    salt := []byte("salt")
    keyLen := 32 // AES-256 需要的密钥长度
    ivLen := 16  // AES 的 IV 长度

    key, iv := EVPBytesToKey(password, salt, keyLen, ivLen)
    fmt.Printf("Key: %x\nIV: %x\n", key, iv)
}

PHP加解密算法使用 openssl 替换 mcrypt 扩展的一个小坑

由于 PHP 7.2 不再支持 mcrypt,因此需要将 mcrypt 替换为 openssl。

但是在替换发现,php 的 openssl 和 mcrypt 实现有一些不同,有几个坑需要注意。

  1. mcrypt 中的 RIJNDAEL_128 算法是 AES 算法的超集,RIJNDAEL_128 的 128 指的是 Block Size,而 AES-128 中的 128 是 Key Size。因此,同是 RIJNDAEL_128 算法,如果你使用的 Key 是 128 位的(16 个字符)那就等同 AES-128。如果 Key 是 192 位的(24 个字符),那就等同与 AES-192。如果 Key 是 256 位的(32 个字符),则等同与 AES-256
  2. 如果使用 RIJNDAEL_256,则无法对应到 AES 算法。因为 AES 固定了 Block Size 是 128,没有 256 的选择
  3. php 的 openssl 实现的 AES 算法,只有 PKCS#7 padding 和 no padding 两个选择,而 mcrypt 默认是 zero padding。因此,如果原先没有手动处理 padding,则切换到 openssl 的时候需要启用 no padding 并手动处理 padding。
  4. 一些函数的替换:mcrypt_get_iv_size  替换为 openssl_cipher_iv_lengthmcrypt_create_iv  替换为 openssl_random_pseudo_bytes

修改前后代码如下:

<?php
define('ENCRYPT_METHOD', 'AES-256-CBC');
define('IV_SIZE', openssl_cipher_iv_length(ENCRYPT_METHOD));

function encrypt_mcrypt($payload, $key)
{
  // 初始化 IV
  $iv = openssl_random_pseudo_bytes(IV_SIZE);
  // 调用加密
  $crypt = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $payload, MCRYPT_MODE_CBC, $iv);
  // 拼接 IV 和 密文
  $combo = $iv . $crypt;
  // 编码拼接后的密文
  $garble = base64_encode($combo);
  return $garble;
}

function decrypt_mcrypt($garble, $key)
{
  // 解码密文
  $combo = base64_decode($garble);
  // 根据 IV SIZE 提取 IV
  $iv = substr($combo, 0, IV_SIZE);
  // 获取剩余密文
  $crypt = substr($combo, IV_SIZE);
  // 解密
  $payload = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $crypt, MCRYPT_MODE_CBC, $iv);
  return $payload;
}

function encrypt_openssl($payload, $key)
{
  // 初始化 IV
  $iv = openssl_random_pseudo_bytes(IV_SIZE);
  // 对密文进行 padding, 16 = 128 / 8
  if (strlen($payload) % 16) {
    $payload = str_pad(
      $payload,
      strlen($payload) + 16 - strlen($payload) % 16,
      "\0"
    );
  }
  // 加密
  $encryptedMessage = openssl_encrypt($payload, ENCRYPT_METHOD, $key, OPENSSL_RAW_DATA | OPENSSL_NO_PADDING, $iv);
  // 编码,拼接 IV
  return base64_encode($iv . $encryptedMessage);
}
function decrypt_openssl($garble, $key)
{
  // 解码密文
  $raw = base64_decode($garble);
  // 根据 IV SIZE 提取 IV
  $iv = substr($raw, 0, IV_SIZE);
  // 获取剩余密文
  $data = substr($raw, IV_SIZE);
  // 解密
  return openssl_decrypt($data, ENCRYPT_METHOD, $key, OPENSSL_RAW_DATA | OPENSSL_NO_PADDING, $iv);
}

$key = md5("password");
echo (decrypt_openssl(encrypt_mcrypt("4db8c44de8f16b4f0c39ef3b38d47c6bdd000082b3b628ee25c20b308d02cf29", $key), $key) . "\n");
echo (decrypt_mcrypt(encrypt_openssl("4db8c44de8f16b4f0c39ef3b38d47c6bdd000082b3b628ee25c20b308d02cf29", $key), $key) . "\n");

 

常用 openssl 命令

The Most Common OpenSSL Commands

One of the most versatile SSL tools is OpenSSL which is an open source implementation of the SSL protocol. There are versions of OpenSSL for nearly every platform, including Windows, Linux, and Mac OS X. OpenSSL is commonly used to create the CSR and private key for many different platforms, including Apache. However, it also has hundreds of different functions that allow you to view the details of a CSR or certificate, compare an MD5 hash of the certificate and private key (to make sure they match), verify that a certificate is installed properly on any website, and convert the certificate to a different format. A compiled version of OpenSSL for Windows can be found here.

If you don’t want to bother with OpenSSL, you can do many of the same things with our SSL Certificate Tools. Below, we have listed the most common OpenSSL commands and their usage:

General OpenSSL Commands

These commands allow you to generate CSRs, Certificates, Private Keys and do other miscellaneous tasks.

  • Generate a new private key and Certificate Signing Request
    openssl req -out CSR.csr -new -newkey rsa:2048 -nodes -keyout privateKey.key
  • Generate a self-signed certificate (see How to Create and Install an Apache Self Signed Certificate for more info)
    openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout privateKey.key -out certificate.crt
  • Generate a certificate signing request (CSR) for an existing private key
    openssl req -out CSR.csr -key privateKey.key -new
  • Generate a certificate signing request based on an existing certificate
    openssl x509 -x509toreq -in certificate.crt -out CSR.csr -signkey privateKey.key
  • Remove a passphrase from a private key
    openssl rsa -in privateKey.pem -out newPrivateKey.pem

Checking Using OpenSSL

If you need to check the information within a Certificate, CSR or Private Key, use these commands. You can also check CSRs and check certificates using our online tools.

  • Check a Certificate Signing Request (CSR)
    openssl req -text -noout -verify -in CSR.csr
  • Check a private key
    openssl rsa -in privateKey.key -check
  • Check a certificate
    openssl x509 -in certificate.crt -text -noout
  • Check a PKCS#12 file (.pfx or .p12)
    openssl pkcs12 -info -in keyStore.p12

Debugging Using OpenSSL

If you are receiving an error that the private doesn’t match the certificate or that a certificate that you installed to a site is not trusted, try one of these commands. If you are trying to verify that an SSL certificate is installed correctly, be sure to check out the SSL Checker.

  • Check an MD5 hash of the public key to ensure that it matches with what is in a CSR or private key
    openssl x509 -noout -modulus -in certificate.crt | openssl md5
    openssl rsa -noout -modulus -in privateKey.key | openssl md5
    openssl req -noout -modulus -in CSR.csr | openssl md5
  • Check an SSL connection. All the certificates (including Intermediates) should be displayed
    openssl s_client -connect www.paypal.com:443

Converting Using OpenSSL

These commands allow you to convert certificates and keys to different formats to make them compatible with specific types of servers or software. For example, you can convert a normal PEM file that would work with Apache to a PFX (PKCS#12) file and use it with Tomcat or IIS. Use our SSL Converter to convert certificates without messing with OpenSSL.

  • Convert a DER file (.crt .cer .der) to PEM
    openssl x509 -inform der -in certificate.cer -out certificate.pem
  • Convert a PEM file to DER
    openssl x509 -outform der -in certificate.pem -out certificate.der
  • Convert a PKCS#12 file (.pfx .p12) containing a private key and certificates to PEM
    openssl pkcs12 -in keyStore.pfx -out keyStore.pem -nodes

    You can add -nocerts to only output the private key or add -nokeys to only output the certificates.

  • Convert a PEM certificate file and a private key to PKCS#12 (.pfx .p12)
    openssl pkcs12 -export -out certificate.pfx -inkey privateKey.key -in certificate.crt -certfile CACert.crt

Originally posted on Sun Jan 13, 2008

https://www.sslshopper.com/article-most-common-openssl-commands.html

常用Openssl命令

申请证书

SSL常用于身份验证、数据加密等应用中,要使用SSL,我们密码有自己的证书。数字证书一般要向专业的认证公司(如VeriSign)申请,并且 都是收费的,某些情况下,我们只是想使用加密的数据通信,而不在乎认证,这时就可以自己制作一个证书,自己制作一个证书,有两种方式,一种是Self Signed,另一种是自己制作一个CA,然后由这个CA,来发布我们需要的证书。下面分别介绍这两个方法。

生成Self Signed证书

# 生成一个key,你的私钥,openssl会提示你输入一个密码,可以输入,也可以不输,
# 输入的话,以后每次使用这个key的时候都要输入密码,安全起见,还是应该有一个密码保护
> openssl genrsa -des3 -out selfsign.key 4096

# 使用上面生成的key,生成一个certificate signing request (CSR)
# 如果你的key有密码保护,openssl首先会询问你的密码,然后询问你一系列问题,
# 其中Common Name(CN)是最重要的,它代表你的证书要代表的目标,如果你为网站申请的证书,就要添你的域名。
> openssl req -new -key selfsign.key -out selfsign.csr

# 生成Self Signed证书 selfsign.crt就是我们生成的证书了
> openssl x509 -req -days 365 -in selfsign.csr -signkey selfsign.key -out selfsign.crt

# 另外一个比较简单的方法就是用下面的命令,一次生成key和证书
> openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout privateKey.key -out certificate.crt

生成自己的CA (Certificate Authority)

CA是证书的发布者,CA可以发布其他人的证书,把CA的证书加入系统信任的根证书后,由CA发布的证书也被系统所信任,所以,CA的key是必须小心保护的,一般都要加密保护,并且限制为root权限读写。

# 生成CA的key
> openssl genrsa -des3 -out ca.key 4096

# 生成CA的证书
> openssl req -new -x509 -days 365 -key ca.key -out ca.crt

# 生成我们的key和CSR这两步与上面Self Signed中是一样的
> openssl genrsa -des3 -out myserver.key 4096
> openssl req -new -key myserver.key -out myserver.csr

# 使用ca的证书和key,生成我们的证书
# 这里的set_serial指明了证书的序号,如果证书过期了(365天后),
# 或者证书key泄漏了,需要重新发证的时候,就要加1
> openssl x509 -req -days 365 -in myserver.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out myserver.crt

查看证书

# 查看KEY信息
> openssl rsa -noout -text -in myserver.key
# 查看CSR信息
> openssl req -noout -text -in myserver.csr

# 查看证书信息
> openssl x509 -noout -text -in ca.crt

# 验证证书
# 会提示self signed
> openssl verify selfsign.crt

# 因为myserver.crt 是幅ca.crt发布的,所以会验证成功
> openssl verify -CAfile ca.crt myserver.crt

去掉key的密码保护

有时候每次都要输入密码太繁琐了,可以把Key的保护密码去掉

> openssl rsa -in myserver.key -out server.key.insecure

不同格式证书的转换

一般证书有三种格式:

  • PEM(.pem) 前面命令生成的都是这种格式,
  • DER(.cer .der) Windows 上常见
  • PKCS#12文件(.pfx .p12) Mac上常见

# PEM转换为DER
> openssl x509 -outform der -in myserver.crt -out myserver.der

# DER转换为PEM
> openssl x509 -inform der -in myserver.cer -out myserver.pem

# PEM转换为PKCS
> openssl pkcs12 -export -out myserver.pfx -inkey myserver.key -in myserver.crt -certfile ca.crt

# PKCS转换为PEM
> openssl pkcs12 -in myserver.pfx -out myserver2.pem -nodes

测试证书

Openssl提供了简单的client和server工具,可以用来模拟SSL连接,做测试使用。

# 连接到远程服务器
> openssl s_client -connect www.google.com.hk:443

# 模拟的HTTPS服务,可以返回Openssl相关信息
# -accept 用来指定监听的端口号
# -cert -key 用来指定提供服务的key和证书
> openssl s_server -accept 443 -cert myserver.crt -key myserver.key -www

# 可以将key和证书写到同一个文件中
> cat myserver.crt myserver.key > myserver.pem
# 使用的时候只提供一个参数就可以了
> openssl s_server -accept 443 -cert myserver.pem -www

# 可以将服务器的证书保存下来
> openssl s_client -connect www.google.com.hk:443 remoteserver.pem
# 转换成DER文件,就可以在Windows下直接查看了
> openssl x509 -outform der -in remoteserver.pem -out remoteserver.cer

计算MD5和SHA1

# MD5 digest
> openssl dgst -md5 filename
# SHA1 digest
> openssl dgst -sha1 filename