【问题标题】:How to programatically find the start and end bytes of LUKS header?如何以编程方式查找 LUKS 标头的开始和结束字节?
【发布时间】:2020-03-18 06:46:05
【问题描述】:

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

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

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

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

注意:提供的解决方案必须对LUKS1(2014 年发布)和LUKS2(2018 年发布)均有效。

在 LUKS1 中,我发现开始是 0,结束是通过将容器二进制标头中的 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 标头结束于字节 4096 * 512 = 2097152

在 LUKS2 中,它要复杂得多,因为它需要在 LUKS 标头中使用 parsing the JSON metadata object

【问题讨论】:

    标签: json linux encryption luks


    【解决方案1】:

    这是一个快速的 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:~# 
    

    【讨论】:

      【解决方案2】:

      Michael 的回答很好,但它是用 2020 年 1 月 EOL 的 python2 编写的,大多数新发行版默认不再包含它。它可以更新,但 cryptsetup 实际上包含针对这种确切情况的命令。只需运行cryptsetup erase &lt;device&gt;,然后输入YES,它将为您清除标题。您可以使用cryptsetup -q erase &lt;device&gt; 跳过确认。

      它确实保留了标头,但它擦除了所有关键插槽,从而实现了在没有标头备份的情况下使数据无法恢复的目标。

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

      【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-08-13
      • 2014-06-08
      • 2012-10-18
      • 1970-01-01
      • 2023-04-02
      • 2010-10-05
      • 2015-12-13
      • 1970-01-01
      相关资源
      最近更新 更多