首页 » 操作系统 » Windows » 正文

AES-256-CBC PKCS7 SHA256 SALT PBKDF2

发布者:站点默认
2011/04/30 浏览数(2,171) 分类:Windows AES-256-CBC PKCS7 SHA256 SALT PBKDF2已关闭评论

本文分别通过 「OpenSSL 命令行」和「php」实现「AES-256-CBC PKCS7 SHA256 SALT PBKDF2」加密与解密,并附有 AutoHotkey 和 Hammerspoon 分别调用 OpenSSL 命令行实现的加密与解密的示例。

关于 OpenSSL 的几点注意事项:

  • 加密后的密文格式为「Salted__ + 8位salt + 密文」,并不是「8位salt + 16位iv + 4位iter + 密文」。
  • iv 和 key 是根据 salt 算出来的,并未存于密文中;
  • 密文中没有 iter,也不通过 salt 计算,而是多方约定的值,解密时的次数值需要与加密时相同;
  • 密码原则上需要32位,但由于 sha256 后使用,所以密码长度可以不是32位;
  • 密文需要 chunk,即每行 64 字符,openssl 命令行解不了超长的单行密文,会报「error reading input file」;

OpenSSL

# windows 命令行时 echo 后的字符串不要加引号!加引号后解密的字符串也有引号。macOS/Linux 加不加引号都可以,解密后没有引号;
# windows 使用 echo 方式只能加密单行文本;macOS/Linux 可以使用 echo -e "Line1\nLine2" 方式使用多行文本;
echo "secret" | openssl enc -aes-256-cbc -base64 -md sha256 -pbkdf2 -iter 1000 -salt -pass pass:YOUR_PASSCODE_STRING
echo "U2FsdGVkX1+U0osoymcY15yBcO+g9i42tg/0EQqaYfc=" | openssl enc -aes-256-cbc -base64 -md sha256 -pbkdf2 -iter 1000 -salt -pass pass:YOUR_PASSCODE_STRING -d
# -p 参数可显示加解密过程中产生的 iv, key

# 以文件的方式加密与解密(去掉 -d 为加密,加上为解密):
openssl enc -aes-256-cbc -base64 -md sha256 -pbkdf2 -iter 1000 -salt -pass pass:YOUR_PASSCODE_STRING -in "/documents/secret.txt" -d

AutoHotkey

v2:

crypt(message, password, isEncrypt := true) {
    tempFile := A_Temp . "\input.txt"
    outFile := A_Temp . "\output.txt"
    if FileExist(tempFile) {
        FileDelete(tempFile)
    }
    if FileExist(outFile) {
        FileDelete(outFile)
    }
    FileAppend(message, tempFile)
    cmd := "cmd /c openssl enc -aes-256-cbc -base64 -md sha256 -pbkdf2 -iter 1000 -salt -pass pass:" . password . ' -in "' . tempFile . '" -out "' . outFile . '"' . (isEncrypt ? " -e" : " -d")
    RunWait(cmd, , "Hide")
    FileRead(&encrypted, outFile)
    FileDelete(tempFile)
    FileDelete(outFile)
    return encrypted
}
; 示例用法
msg := "your_message_here"
pwd := "your_password_here"
result := crypt(msg, pwd, true)
result := result != "" ? result : msg
MsgBox("加密结果:`n" . result)
decrypted := crypt(result, pwd, false) ; 密文需要 chunked (每行64字符换行)用于解密外部的密文
decrypted := decrypted != "" ? decrypted : result
MsgBox("解密结果:`n" . decrypted)

v1:

crypt(message, password, isEncrypt := true) {
    tempFile := A_Temp . "\input.txt"
    FileDelete, %tempFile%
    FileAppend, %message%, %tempFile%
    outFile := A_Temp . "\output.txt"
    FileDelete, %outFile%
    cmd := "cmd /c openssl enc -aes-256-cbc -base64 -md sha256 -pbkdf2 -iter 1000 -salt -pass pass:" . password . " -in """ . tempFile . """ -out """ . outFile . """" . (isEncrypt ? " -e" : " -d")
    RunWait, %cmd%, , Hide
    FileRead, encrypted, %outFile%
    FileDelete, %tempFile%
    FileDelete, %outFile%
    return encrypted
}
; 示例用法
msg := "your_message_here"
pwd := "your_password_here"
result := crypt(msg, pwd, true)
result := result != "" ? result : msg
MsgBox, 加密结果:`n%result%
decrypted := crypt(result, pwd, false) ; 密文需要 chunked (每行64字符换行)用于解密外部的密文
decrypted := decrypted != "" ? decrypted : result
MsgBox, 解密结果:`n%decrypted%

注:文章 同步剪贴板内容 AutoHotkey 需要这里的代码。

为什么使用文件而不是 echo “string” ?因为 windows 的 echo 没有 -e 参数,处理不了有换行的多行文本。echo 多行文本时遇到换行就执行命令了,走不到后边的 openssl 。

另,以上代码使用 RunWait 调用 OpenSSL 实现,如果必须用纯 AutoHotkey 实现(不调用 openssl.exe),需要:

  1. 实现 PBKDF2-HMAC-SHA256(可用 AHK 脚本或 DLL)
  2. 实现 AES-256-CBC(可用 AHK 脚本或 DLL)
  3. 实现 PKCS7 填充
  4. 拼接 Salted__ + salt + 密文

AutoHotkey 原生不支持上边这些算法,通常需要调用外部 DLL(如 OpenSSL DLL、Crypt32.dll、bcrypt.dll),或者用 COM 对象(如 CAPICOM),但 CAPICOM 不支持 PBKDF2-HMAC-SHA256。所以最终决定用 openssl.exe 命令行加密与解密而 AutoHotkey 只负责自动化。

Hammerspoon lua

function crypt(message, password, isEncrypt)
    -- 1. 写入临时文件
    local tmpfile = os.tmpname()
    local f = io.open(tmpfile, "w")
    f:write(message)
    f:close()
    -- 2. 构造 openssl 命令
    local mode = isEncrypt == false and "-d" or "-e"
    local cmd = string.format(
        [[openssl enc -aes-256-cbc -base64 -md sha256 -pbkdf2 -iter 1000 -salt -pass pass:%s -in "%s" %s]],
        password, tmpfile, mode
    )
    -- 3. 执行命令
    local output, status = hs.execute(cmd, true)
    if nil == status then                                                                                                   
      local message = hs.styledtext.new(
        'Error: crypt failed. [code:' .. rc ..  ']',
        { 
          color = hs.drawing.color.asRGB(
            { 
              hex = '#FF0000',
              alpha = 1 
            } 
          ),
          font = { 
            name = 'Monaco',
            size = 14
          } 
        } 
      ) 
      hs.alert.show(message);
    end 
    -- 4. 删除临时文件
    os.remove(tmpfile)

    -- 5. 返回结果
    return status and output or nil
end

-- 示例用法
local message = [[
line1
line2
line3
]]
local password = "your_password_here"

-- 加密
local encrypted = crypt(message, password)
if encrypted then
    hs.alert.show("加密结果:\n" .. encrypted)
    print("加密结果:\n" .. encrypted)
else
    hs.alert.show("加密失败")
    print("加密失败")
end

-- 解密
local decrypted = crypt(encrypted, password, false)
if decrypted then
    hs.alert.show("解密结果:\n" .. decrypted)
else
    hs.alert.show("解密失败")
end

注:使用 openssl.exe 命令加密与解决,Hammerspoon 只负责自动化,理由同上边 AutoHotkey。另,同步剪贴板内容 Hammerspoon需要这里的代码。

PHP

// 加密函数
function encrypt($data, $password, $iterations = 1000) {
    $salt = random_bytes(8); // 生成指定长度的随机盐值
    $derived = deriveKeyAndIV($password, $salt, $iterations); // 派生密钥和 IV
    $key = $derived['key'];
    $iv = $derived['iv'];
    $encrypted = openssl_encrypt($data, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv); // 加密数据
    $encryptedBase64 = base64_encode("Salted__" . $salt . $encrypted); // 构造 OpenSSL 格式的密文并 Base64 编码
    $encryptedWithNewLine = chunk_split($encryptedBase64, 64, "\n"); // 必须 chunk 否则命令行的 openssl 不认识
    return $encryptedWithNewLine;
}

// 解密函数
function decrypt($encryptedData, $password, $iterations = 1000) {
    $data = base64_decode($encryptedData); // 解码 Base64 数据
    if (substr($data, 0, 8) !== "Salted__") {
        throw new Exception("Invalid encrypted data format.");
    } 
    $salt = substr($data, 8, 8); // 提取盐值(8 字节)
    $encrypted = substr($data, 16); // 提取加密数据
    $derived = deriveKeyAndIV($password, $salt, $iterations); // 派生密钥和 IV
    $key = $derived['key'];
    $iv = $derived['iv'];
    return openssl_decrypt($encrypted, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv); // 解密数据
}

// 使用 PBKDF2 派生密钥和 IV
function deriveKeyAndIV($password, $salt, $iterations = 1000, $keySize = 32, $ivSize = 16) {
    $derived = hash_pbkdf2('sha256', $password, $salt, $iterations, $keySize + $ivSize, true);
    $key = substr($derived, 0, $keySize);
    $iv = substr($derived, $keySize, $ivSize);
    return ['key' => $key, 'iv' => $iv];
}
点击返回顶部
  1. 留言
  2. 联系方式