如何以编程方式找到 LUKE header 的开始和结束字节?

How to programatically find the start and end bytes of LUKS header?

如何以编程方式确定块存储设备上 LUKS header 的准确起始字节和准确结束字节?

我使用 Linux 统一密钥设置 (LUKS) 进行全盘加密 (FDE),因此我驱动器上的所有数据都使用不是从我的密码派生的强主密钥加密,而且我我正在编写一个脚本,该脚本将在 panic/emergency 关机情况下安全擦除驱动器(即:有人偷了您的笔记本电脑)。

假设我有一个 1000T 的驱动器,并且在我的紧急情况下时间太短(~30 秒)无法实际用随机字节填充驱动器。相反,我只想覆盖 header,因为如果 header(包含带有主密钥和盐的密钥槽)丢失,驱动器上的所有数据都毫无价值——即使通过 rubber-hose 密码分析恢复了密码。

如何以编程方式安全地确定 LUKS header 的起始字节和结束字节,以便我知道要覆盖什么?

Note: The solution provided must be valid for both LUKS1 (released in 2014) and LUKS2 (released in 2018).

在 LUKS1 中,我发现开始是 0,结束是通过将容器二进制 header 中的 payload-offset 字段乘以 512 来确定的。例如

root@disp4117:~# hexdump -Cs 6 -n 2 luksVol1
00000006  00 01                                             |..|
00000008
root@disp4117:~# hexdump -Cs 104 -n 4 luksVol1
00000068  00 00 10 00                                       |....|
0000006c
root@disp4117:~# 

在这种情况下,LUKS1 header 结束于字节 4096 * 512 = 2097152

在 LUKS2 中,它要复杂得多,因为在 LUKS header.

中需要 parsing the JSON metadata object

这是一个快速 python 脚本,它将输出给定 LUKS 容器的开始和结束字节:

#!/usr/bin/python
################################################################################
# File:    luksHeaderBounds.py
# Purpose: Determine the start and end bytes of a LUKS header
# Authors: Michael Altfield <michael@michaelaltfield.net>
# Created: 2020-03-18
# Updated: 2020-03-18
# Version: 0.1
################################################################################

# DEPENDS
import sys, struct, json;

# MAIN BODY

if len(sys.argv) != 2:
    print "Usage: luksHeaderBounds.py <device>"
    exit(1)

device = sys.argv[1]

# first we get the LUKS version
with open(device, "rb") as f:

    # the LUKS version field is a big endian unsigned short (uint16_t = '>H')
    #  * https://docs.python.org/2/library/struct.html

    # the LUKS version field starts at offset 6 bytes and is 2 bytes long
    # so we get the first 8 bytes and the last 2 bytes of that
    #  * https://gitlab.com/cryptsetup/LUKS2-docs/blob/master/luks2_doc_wip.pdf

    luksVersion = struct.unpack( '>H', f.read(8)[-2:] )[0];

if luksVersion == 1:
    # LUKS1 - https://gitlab.com/cryptsetup/cryptsetup/-/wikis/LUKS-standard/on-disk-format.pdf

    with open(device, "rb") as f:

        # the payload-offset field is a big endian unsigned long (uint32_t = '>L')
        #  * https://docs.python.org/2/library/struct.html

        # in LUKS1, the payload-offset field starts at offset 104 bytes and is 4 bytes long
        # so we get the first 108 bytes and the last 4 bytes of that
        #  * https://gitlab.com/cryptsetup/cryptsetup/-/wikis/LUKS-standard/on-disk-format.pdf

        payloadOffset = struct.unpack( '>L', f.read(108)[-4:] )[0];

    # the payload-offset is just the number of (512-byte sized) sectors
    # to get bytes we must multiply it by 512
    luksHeaderEnd = 512 * payloadOffset

elif luksVersion == 2:
    # LUKS2 - https://gitlab.com/cryptsetup/LUKS2-docs/blob/master/luks2_doc_wip.pdf

    # first we get the header size from the primary binary header
    with open(device, "rb") as f:

        # the hdr_size field is a big endian unsigned long long (uint64_t = '>Q')
        #  * https://docs.python.org/2/library/struct.html

        # in LUKS2, the hdr_size field starts at offset 8 bytes and is 8 bytes long
        # so we get the first 16 bytes and the last 8 bytes of that
        #  * https://gitlab.com/cryptsetup/LUKS2-docs/blob/master/luks2_doc_wip.pdf

        hdr_size=struct.unpack( '>Q', f.read(16)[-8:] )[0];

    # the JSON plaintext metadata object starts after the header (4096 bytes)
    # and ends at (hdr_size - 4096)
    jsonSize = hdr_size - 4096;

    with open(device, "rb") as f:
        luksMetadata = f.read(hdr_size)[-jsonSize:];

    decoder = json.JSONDecoder()
    jsonMetadata = decoder.raw_decode(luksMetadata)

    # the LUKS2 header ends at the start of the first data segment
    luksHeaderEnd=jsonMetadata[0]['segments']['0']['offset']

else:

    print "ERROR: Unable to determine LUKS version"
    exit(1)

print "Device: " +str(device)
print "  LUKS Header Start Byte: " +str(0)
print "  LUKS Header End Byte:   " +str(luksHeaderEnd)

exit(0)

以及 LUKS1 卷的示例执行:

root@disp4117:~# ./luksHeaderBounds.py luksVol1
Device: luksVol1
  LUKS Header Start Byte: 0
  LUKS Header End Byte:   2097152
root@disp4117:~# 

以及 LUKS2 卷的示例执行:

root@disp4117:~# ./luksHeaderBounds.py luksVol2
Device: luksVol2
  LUKS Header Start Byte: 0
  LUKS Header End Byte:   16777216
root@disp4117:~# 

Michael 的回答很好,但它是用 python2 写的,它在 2020 年 1 月停产,大多数新发行版不再默认包含它。它可以更新,但 cryptsetup 实际上包括针对这种确切情况的命令。只需 运行 cryptsetup erase <device>,然后输入 YES,它会为您清除 header。您可以使用 cryptsetup -q erase <device>.

跳过确认

它确实保留了 header 但它擦除所有密钥槽,从而实现 objective 使数据在没有 header 备份的情况下无法恢复。

这是手册页 https://man.archlinux.org/man/cryptsetup.8