AnyMath 文档
Notebook

具有任意数据的文件的加密和解密

导言

现代数据交换通常通过开放渠道进行:电子邮件,即时信使,云存储,公共Wi-Fi网络。 在这些条件下,任何文件-文档,图像,存档-都可以在发送给收件人的途中被截取,复制或修改。 保护的任务归结为两个独立的要求:保持机密性和确保完整性。

本文提出了一种针对任何类型文件的紧凑加密方案:在CBC模式下使用对称AES-256算法对文件名进行加密,并通过HMAC-SHA256认证码确认包装容器的真实性。 在该方案中,只有在成功验证标记之后才执行解密。 在接收端,恢复源文件,如果名称匹配,则选择带有索引的空闲名称。

加入图书馆

这项工作中使用了两个库:

*Nettle-AES,CBC模式,HMAC-SHA256和辅助块添加操作的实现;

*Random是用于salt和初始化向量的密码随机值生成器。

In [ ]:
for lib in ["Nettle", "Random"]
    try
        eval(Meta.parse("using $lib"))
    catch ex
        import Pkg
        Pkg.add(lib)
        eval(Meta.parse("using $lib"))
    end
end

加密功能

功能 enc_file(fnam, pswd) 接受当前目录中源文件的名称和密码,创建二进制数据容器并返回其名称。

文件名转换为字节并打包到固定长度缓冲区(256字节)中,可用空间用零填充。 缓冲区用文件的内容补充,从而获得单个有效载荷。 这使得可以毫不含糊地破译名称结束和内容开始的地方:边界由结构本身设置。

然后生成盐和初始化向量。 两个独立的密钥从密码和盐派生:256位AES密钥和HMAC的单独密钥。 有效载荷根据PKCS#5方案进行补充,并在CBC模式下使用AES-256算法进行加密。 HMAC-SHA256标签是从容器主体(盐,向量,密文)计算出来的,证明内容没有被改变。 容器主体和标签写入到 .bin-一个文件,其名称是根据现有的文件(如果在当前目录中有)选择的,这样旧的容器就不会被复盖。

In [ ]:
function enc_file(fnam, pswd)
    size = 256   
    namb = Vector{UInt8}(basename(fnam))
    if length(namb) > size
        error("Имя файла слишком длинное: ", length(namb), " байт (максимум ", size, ")")
    end
    nbuf = zeros(UInt8, size)
    nbuf[1:length(namb)] .= namb
    salt = rand(UInt8, 8)
    iv   = rand(UInt8, 16)
    (key, _) = gen_key32_iv16(Vector{UInt8}(pswd), salt)
    hmac = HMACState("sha256", salt)
    Nettle.update!(hmac, Vector{UInt8}(pswd))
    body = vcat(salt, iv, encrypt(Encryptor("AES256", key), :CBC, iv, add_padding_PKCS5(vcat(nbuf, read(fnam)), 16)))
    hmac = HMACState("sha256", Nettle.digest!(hmac))
    Nettle.update!(hmac, body)
    base, _ = splitext(fnam)
    cand = string(base, ".bin")
    indx = 1
    while isfile(joinpath(pwd(), cand))
        cand = string(base, "(", indx, ").bin")
        indx += 1
    end

    write(cand, vcat(body, Nettle.digest!(hmac)))
    return cand
end
Out[0]:
enc_file (generic function with 1 method)

解密函数

功能 dec_file(binf, pswd) 接受名称 .bin-容器和密码,恢复原始文件并返回恢复文件的名称。

容器分为主体和标签。 容器体中提取所述盐、初始化向量和密文。 这两个密钥都是使用盐和密码生成的。 如果密码正确,它们将与加密期间使用的密码相匹配。

接下来是完整性检查:HMAC标签在容器体上重新计算,并与传输的标签进行比较。 如果不匹配,函数会立即抛出错误,并且不会对数据进行解密。 此过程关闭cbc模式下的一类攻击,在这种模式下,攻击者试图通过观察收件人对损坏数据包的反应来解密消息。

验证成功后,将删除PKCS#5补充,并解密有效负载。 前256个字节是名称的缓冲区,其余是文件的内容。 名称的零补码被切断,名称被恢复,并且在发生碰撞的情况下,选择具有索引的自由名称。 该文件将写入当前目录,并作为结果返回其名称。

In [ ]:
function dec_file(binf, pswd)
    size = 256
    allb = read(binf)
    body = allb[1:end-32]
    salt = body[1:8]
    (key, _) = gen_key32_iv16(Vector{UInt8}(pswd), salt)
    hmac = HMACState("sha256", salt)
    Nettle.update!(hmac, Vector{UInt8}(pswd))
    hmac = HMACState("sha256", Nettle.digest!(hmac))
    Nettle.update!(hmac, body)
    if Nettle.digest!(hmac) != allb[end-31:end]
        error("ОШИБКА: Данные повреждены или подделаны.")
    end
    payl = trim_padding_PKCS5(decrypt(Decryptor("AES256", key), :CBC, body[9:24], body[25:end]))
    if length(payl) < size
        error("ОШИБКА: Контейнер повреждён.")
    end
    bufr = payl[1:size]
    lend = findlast(!iszero, bufr)
    if lend === nothing
        error("ОШИБКА: Контейнер повреждён.")
    end
    fnam = String(bufr[1:lend])
    base, ext = splitext(fnam)
    cand = fnam
    indx = 1
    while isfile(joinpath(pwd(), cand))
        cand = string(base, "(", indx, ")", ext)
        indx += 1
    end
    write(cand, payl[size+1:end])
    return cand
end
Out[0]:
dec_file (generic function with 1 method)

加密技术

选择要加密的文件 Данные.pdf,以pdf格式包含此示例。 设置文件名, пароль,让我们转向加密功能。

In [ ]:
имя_файла = "Данные.pdf"
пароль = "qwerty12345678";
enc_file(имя_файла, пароль)
Out[0]:
"Данные.bin"

作为加密的结果,创建了一个二进制文件。 Данные.bin.

数据传输

删除源文件 Данные.pdf;删除包含其名称的变量和 пароль 为加密功能。 想象一下,一个加密的文件通过一个开放的通信信道传输给接收者。

In [ ]:
!isnothing(имя_файла) && isfile(имя_файла) && rm(имя_файла)
имя_файла = nothing
пароль = nothing

解码

让我们想象一下,我们现在站在收件人一边。 我们已经接受了一个加密的文件。 Данные.bin,是我们事先知道的 пароль. 让我们设置文件名和密码,并转向解密功能。

In [ ]:
имя_файла = "Данные.bin"
пароль = "qwerty12345678";
dec_file(имя_файла, пароль)
Out[0]:
"Данные.pdf"

作为解密的结果,源文件 Данные.pdf恢复了。

结论

此示例实现了一个完整的方案,用于通过开放数据通道安全传输任何类型的文件。 对称AES-256-CBC加密可确保文件内容和名称的机密性。 加密-然后-MAC方案在解密之前进行标记验证,消除了CBC模式容易受到的攻击,并确保没有一个修改过的字节被忽视。 发生冲突时的自动命名使恢复安全且可预测。

这些方案是许多真实系统的基础。 使HTTPS工作的TLS协议使用类似的方法。 安全信使使用具有强制身份验证的单独密钥加密每条消息。 磁盘加密系统(LUKS、BitLocker、FileVault)使用具有扇区完整性验证的块密码。 安全电子邮件、VPN隧道、电子文档管理和备份系统都基于相同的原则。

这项工作的实用价值还在于所有代码都可用于验证:您可以确切地看到使用了哪些算法,如何生成密钥,如何检查完整性以及每个步骤的数据发生了什 当使用第三方加密应用程序出于任何原因都是不希望的,例如:由于封闭的源代码,不明确的威胁模型,过度的功能或对执行环境的要求,脚本可以直接应用或作为其自己的解决方案的基础。