LUKSのヘッダ領域を壊しても復旧できるのか試した

要約

CryptsetupのFAQを読むといいでしょう

https://gitlab.com/cryptsetup/cryptsetup/-/wikis/FrequentlyAskedQuestions#6-backup-and-data-recovery

試してわかったこと

  • LUKSのヘッダ領域は2か所で冗長化されている模様。どちらかが壊れると片方を使って修復しようとする模様。
  • 2か所を壊すとopenできなくなる
  • luksHeaderBackupでバックアップファイルを取っておくことができ、luksHeaderRestoreでバックアップファイルから修復できる

というわけで、LUKSの設定が終わったりluksAddKey等で変更したら、LUKSのヘッダ領域もバックアップしておくのがよいでしょう。

以降はやってみた記事。

背景

LUKSが何たるかはGihyoの記事などを参照。

gihyo.jp

LUKSの実装であるdm-cryptはデバイスの領域を一部使って、暗号化に使う鍵情報などを持ったりして、デバイスのデータの暗号化ができる。cryptsetupはそのお助けツール。

LUKSの仕様上、パスフレーズや鍵ファイルなど複数の手段で暗号化を行っているキー(マスターキー)を取得できるようになっているっぽい。

しかし、そこの領域が壊れたら読み込めなくなるのでは?復旧できなくなってしまうのではないか?と思い調べたらバックアップができるようだったので試した。

色々やってますが、やることは luksHeaderBackupでバックアップファイルを取って、luksHeaderRestoreでバックアップファイルから復旧できることを確認しているだけです。

やってみた

環境情報。一応、Hyper-V上のVMです。

$ uname -a
Linux test3 6.11.0-19-generic #19-Ubuntu SMP PREEMPT_DYNAMIC Wed Feb 12 21:43:43 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux

$ cat /etc/os-release
PRETTY_NAME="Ubuntu 24.10"
NAME="Ubuntu"
VERSION_ID="24.10"
VERSION="24.10 (Oracular Oriole)"
VERSION_CODENAME=oracular
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=oracular
LOGO=ubuntu-logo

$ cryptsetup --version
cryptsetup 2.7.2 flags: UDEV BLKID KEYRING FIPS KERNEL_CAPI HW_OPAL

cryptsetupを使います。

ハードディスクを用意するのは大変なので、ファイルを用意して、ループバックデバイスとして使います。

# 1GBのブロックデバイスを準備
sudo dd if=/dev/zero of=/var/tmp/luks-test.img bs=1M count=1024
ls -l /var/tmp/luks-test.img

# loopback deviceとして登録
sudo losetup /dev/loop0 /var/tmp/luks-test.img

# ループバックデバイスの内容を確認
losetup /dev/loop0 

# とりあえず暗号化

sudo cryptsetup luksFormat /dev/loop0

# 暗号鍵ファイルを作成
dd if=/dev/urandom of=rand.key bs=1024 count=1

# 暗号鍵ファイルを登録(要パスフレーズ入力)
sudo cryptsetup luksAddKey /dev/loop0 ./rand.key

# keyslotsが2つあること
sudo cryptsetup luksDump /dev/loop0

# 暗号デバイスを開く
sudo cryptsetup open /dev/loop0 crypt-luks-test  --key-file ./rand.key


# フォーマットしてマウント
sudo mkfs.ext4 /dev/mapper/crypt-luks-test
sudo mkdir -p /mnt/luks-test
sudo mount /dev/mapper/crypt-luks-test /mnt/luks-test

# ファイルを書き込んで読みだしてみる
echo "$(date --iso-8601=ns): hello world" | sudo tee /mnt/luks-test/hello.txt
cat /mnt/luks-test/hello.txt
# => 2025-03-26T16:50:39,133817076+00:00: hello world

ここまででLuksで暗号化されたデバイスにファイル読み書きができた。

# Luksヘッダのバックアップ
sudo cryptsetup luksHeaderBackup /dev/loop0 --key-file ./rand.key --header-backup-file luks-test.header

# 生成確認。16MBぐらいありそう。
ls -l luks-test.header
# -r-------- 1 root root 16777216 Mar 26 14:18 luks-test.header

# チェックサム確認(16MBまで一致)
sudo cat luks-test.header | head -c $((1024*1024*16)) | sha256sum
# => 20fa8976c42aaa1fbb9c66743b4b9f8eeaf2cb9ea94a3dd5d09ba56af69f4625  -
sudo cat /dev/loop0 | head -c $((1024*1024*16)) | sha256sum
# => 20fa8976c42aaa1fbb9c66743b4b9f8eeaf2cb9ea94a3dd5d09ba56af69f4625  -

# 16MB+1Byteで不一致しはじめた。同じファイルを見てない、ということがわかる
sudo cat luks-test.header | head -c $((1024*1024*16+1)) | sha256sum
# => 20fa8976c42aaa1fbb9c66743b4b9f8eeaf2cb9ea94a3dd5d09ba56af69f4625  -
sudo cat /dev/loop0 | head -c $((1024*1024*16+1)) | sha256sum
# => 8e5d19c21ae5703578697eb3f77c8e9aa4a168308c6e1fdd86a6a08051aa31df  -

# ヘッダが同じ構造なのでluksDumpも動く
sudo cryptsetup luksDump ./luks-test.header

ではバックアップもとったし、壊そう、と思ったが、簡単には壊れなかった。

$ sudo hexdump -C /dev/loop0 | head -n 2
00000000  4c 55 4b 53 ba be 00 02  00 00 00 00 00 00 40 00  |LUKS..........@.|
00000010  00 00 00 00 00 00 00 04  00 00 00 00 00 00 00 00  |................|

$ sudo dd if=/dev/urandom of=/dev/loop0 bs=1024 count=1
1+0 records in
1+0 records out
1024 bytes (1.0 kB, 1.0 KiB) copied, 0.000379041 s, 2.7 MB/s

# 先頭がランダムデータになった
$ sudo hexdump -C /dev/loop0 | head -n 2
00000000  7d cd 33 21 76 2b 80 61  e5 0d 33 0d 85 89 a9 d7  |}.3!v+.a..3.....|
00000010  29 48 2e a4 b6 11 74 c4  83 ab 76 ab 9b b1 b5 7d  |)H....t...v....}|

# ランダムデータを書き込んだのに普通に読めてしまう
$ sudo cryptsetup luksDump /dev/loop0
LUKS header information
Version:        2
Epoch:          4
Metadata area:  16384 [bytes]
Keyslots area:  16744448 [bytes]
UUID:           3a3e928a-1c14-4024-bf3d-8f5fe6f4043f
........
                    99 40 a5 53 f8 3c db da 92 f5 3c e0 a1 5c 7c 83

# そしてluksDumpした後は、内容が戻ってしまう
$ sudo hexdump -C /dev/loop0 | head -n 2
00000000  4c 55 4b 53 ba be 00 02  00 00 00 00 00 00 40 00  |LUKS..........@.|
00000010  00 00 00 00 00 00 00 04  00 00 00 00 00 00 00 00  |................|

# また、こんな感じに1KBづつずらしながら壊して読み直す程度では修復できてしまう模様
$ for i in {0..1000}; do
  echo "---$i---"
  sudo dd if=/dev/urandom of=/dev/loop0 bs=1024 count=1 seek=${i}K
  # 壊れたか確認
  diff <(sudo cryptsetup luksDump /dev/loop0) <(sudo cryptsetup luksDump ./luks-test.header) || break
done

もしかして修復できるようにデータを2重にもってるんじゃないか?と思ってhexdumpで眺めてたらそれらしいことをやってた。

00000000  4c 55 4b 53 ba be 00 02  00 00 00 00 00 00 40 00  |LUKS..........@.|
00000010  00 00 00 00 00 00 00 04  00 00 00 00 00 00 00 00  |................|
00000020  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000040  00 00 00 00 00 00 00 00  73 68 61 32 35 36 00 00  |........sha256..|
00000050  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000060  00 00 00 00 00 00 00 00  b4 15 f1 26 c4 f8 35 79  |...........&..5y|
00000070  b7 72 c1 8b 3e d6 d1 1e  a6 99 95 ca 0a 45 17 ba  |.r..>........E..|
00000080  12 7e 47 90 7a d5 06 eb  12 88 20 6f 2c 3e a5 dd  |.~G.z..... o,>..|
00000090  8c 88 47 fe d3 65 19 8d  f7 54 af 2c aa 55 e0 8e  |..G..e...T.,.U..|
000000a0  80 18 aa e0 0d d9 00 92  33 61 33 65 39 32 38 61  |........3a3e928a|
000000b0  2d 31 63 31 34 2d 34 30  32 34 2d 62 66 33 64 2d  |-1c14-4024-bf3d-|
000000c0  38 66 35 66 65 36 66 34  30 34 33 66 00 00 00 00  |8f5fe6f4043f....|
000000d0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
000001c0  8c 1e 30 ca b5 52 0b 67  ca a7 6c 51 f8 12 6d e7  |..0..R.g..lQ..m.|
000001d0  96 70 fc d6 be b0 78 17  80 c7 0a e8 38 fd 22 29  |.p....x.....8.")|
000001e0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00001000  7b 22 6b 65 79 73 6c 6f  74 73 22 3a 7b 22 30 22  |{"keyslots":{"0"|
00001010  3a 7b 22 74 79 70 65 22  3a 22 6c 75 6b 73 32 22  |:{"type":"luks2"|
00001020  2c 22 6b 65 79 5f 73 69  7a 65 22 3a 36 34 2c 22  |,"key_size":64,"|
00001030  61 66 22 3a 7b 22 74 79  70 65 22 3a 22 6c 75 6b  |af":{"type":"luk|
00001040  73 31 22 2c 22 73 74 72  69 70 65 73 22 3a 34 30  |s1","stripes":40|

...

00001420  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00004000  53 4b 55 4c ba be 00 02  00 00 00 00 00 00 40 00  |SKUL..........@.|
00004010  00 00 00 00 00 00 00 04  00 00 00 00 00 00 00 00  |................|
00004020  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00004040  00 00 00 00 00 00 00 00  73 68 61 32 35 36 00 00  |........sha256..|
00004050  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00004060  00 00 00 00 00 00 00 00  16 61 75 c0 8d 5b 95 fe  |.........au..[..|
00004070  f3 be e7 21 8d c2 c3 f5  fd 18 30 ee 01 07 b5 65  |...!......0....e|
00004080  87 69 bd de 7a c6 1e a5  d9 86 7c 03 55 d9 95 07  |.i..z.....|.U...|
00004090  f5 32 d5 2f 0e 30 f1 e5  18 23 3a 20 35 c5 81 d1  |.2./.0...#: 5...|
000040a0  fc c9 c2 3f 1c 05 83 df  33 61 33 65 39 32 38 61  |...?....3a3e928a|
000040b0  2d 31 63 31 34 2d 34 30  32 34 2d 62 66 33 64 2d  |-1c14-4024-bf3d-|
000040c0  38 66 35 66 65 36 66 34  30 34 33 66 00 00 00 00  |8f5fe6f4043f....|
000040d0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00004100  00 00 00 00 00 00 40 00  00 00 00 00 00 00 00 00  |......@.........|
00004110  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
000041c0  ff 48 af ee f1 cc b3 5b  77 a5 bd 3d 9c 23 e3 32  |.H.....[w..=.#.2|
000041d0  07 3c 90 85 0c 1f 8d 19  70 8f 03 68 34 46 a8 04  |.<......p..h4F..|
000041e0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00005000  7b 22 6b 65 79 73 6c 6f  74 73 22 3a 7b 22 30 22  |{"keyslots":{"0"|
00005010  3a 7b 22 74 79 70 65 22  3a 22 6c 75 6b 73 32 22  |:{"type":"luks2"|
00005020  2c 22 6b 65 79 5f 73 69  7a 65 22 3a 36 34 2c 22  |,"key_size":64,"|
00005030  61 66 22 3a 7b 22 74 79  70 65 22 3a 22 6c 75 6b  |af":{"type":"luk|
00005040  73 31 22 2c 22 73 74 72  69 70 65 73 22 3a 34 30  |s1","stripes":40|

今回は0x4000(16384)あたりにマジックナンバー違いで同じデータがある模様。どっちかが壊れていたら読み直すのだろう。

ということで、2か所壊すこととした。

# 0x0000 と 0x4000 から1KiBづつ壊す
sudo dd if=/dev/urandom of=/dev/loop0 bs=1 count=1024
sudo dd if=/dev/urandom of=/dev/loop0 bs=1 count=1024 seek=16384

# 壊したことを確認
sudo hexdump -C /dev/loop0 | less
# 00000000  d7 06 15 76 8c 49 ad f4  cd 59 11 cb c6 ed 61 82  |...v.I...Y....a.|
# 00000010  cd 07 4d 6f bd 25 f3 c2  13 e4 b0 9a 47 ff f0 e7  |..Mo.%......G...|
# ...
# 00004000  33 40 f6 da 8d c0 4c 96  1d b3 58 4a 29 94 94 4e  |3@....L...XJ)..N|
# 00004010  54 cd a4 13 ee 21 32 b9  c1 62 db b1 21 7c 75 61  |T....!2..b..!|ua|

そしてluksDumpが機能しなくなった。データはまだ読めたけど、一度デバイスを閉じてしまうと開けなくなった。

# 壊れた
sudo cryptsetup luksDump /dev/loop0
# => Device /dev/loop0 is not a valid LUKS device.

# ファイルはまだ読めた。復号化後にLUKSのヘッダが壊れたぐらいなら読めそう(メモリか何かに持ってるんだろう)
sync
sudo tee /proc/sys/vm/drop_caches <<< "3"
cat /mnt/luks-test/hello.txt
# => 2025-03-26T16:50:39,133817076+00:00: hello world

# 追記もできる
echo "$(date --iso-8601=ns): hello world" | sudo tee -a /mnt/luks-test/hello.txt
cat /mnt/luks-test/hello.txt
# => 2025-03-26T16:50:39,133817076+00:00: hello world
# => 2025-03-26T16:52:48,194830547+00:00: hello world

# 一度アンマウントして、close
sudo umount /mnt/luks-test
sudo cryptsetup close crypt-luks-test

# 再度openしようとすると失敗
sudo cryptsetup open /dev/loop0 crypt-luks-test --key-file ./rand.key
# => Device /dev/loop0 is not a valid LUKS device.

というわけで困った。頼みの綱のバックアップの出番。

# 戻せるかな?
sudo cryptsetup luksHeaderRestore /dev/loop0  --header-backup-file ./luks-test.header

確認が出てくるのでYESを応答。

$ sudo cryptsetup luksHeaderRestore /var/tmp/luks-test.img --header-backup-file ./luks-test.header
Device /var/tmp/luks-test.img is too small. Need at least 16777216 bytes.

WARNING!
========
Device /var/tmp/luks-test.img does not contain LUKS2 header. Replacing header can destroy data on that device.

Are you sure? (Type 'yes' in capital letters): YES

確認してみよう。

# luksDump も通るようになった
sudo cryptsetup luksDump /dev/loop0

# チェックサムも元通り
sudo cat /dev/loop0 | head -c $((1024*1024*16)) | sha256sum
# => 20fa8976c42aaa1fbb9c66743b4b9f8eeaf2cb9ea94a3dd5d09ba56af69f4625  -

# マウントして確認(暗号鍵ファイルではなくパスフレーズで)
sudo cryptsetup open /dev/loop0 crypt-luks-test
sudo mount /dev/mapper/crypt-luks-test /mnt/luks-test

# ファイルも読めた
sync
sudo tee /proc/sys/vm/drop_caches <<< "3"
cat /mnt/luks-test/hello.txt
# => 2025-03-26T16:50:39,133817076+00:00: hello world
# => 2025-03-26T16:52:48,194830547+00:00: hello world

LUKSヘッダ領域を上書きする感じっぽい。単純な仕組みですね。

以上。ここまで。

バックアップはどうすれば?

  • バックアップファイルは別途暗号化する
  • クラウドUSBメモリなど切り離された領域に置いておく

パスフレーズや暗号鍵ファイルなどがわかるとマスターキーがゲットできてしまうはずなので。

おまけ: ついでにデータ領域を壊してみた

以降は、興味本位だけど、データを壊したらどうなるか試す。

LUKSは暗号化の仕組みだし、データは冗長化してないから普通に壊れるはず。

# 復号化済みデバイスのオフセット位置を探る
sudo grep -abo "hello world" /dev/mapper/crypt-luks-test
# => 136314917:hello world

# データ領域まで書き込んでみるLUKSのデータ領域分(16MiB)を足せばデータ領域になるかな?
OFFSET=$((136314917 + 1024*1024*16))
sudo dd if=/dev/urandom of=/dev/loop0 bs=1 count=1024 seek=$OFFSET

# データ壊れちゃった(キャッシュが効いてたのでクリアすること)
sudo tee /proc/sys/vm/drop_caches <<< "3"
cat /mnt/luks-test/hello.txt
# => 2025-03-26T16:50:39,133817076+00T
#                                 _�v���da>���dd

壊れた(キャッキャ)

データ自体はRAID冗長化するのがよいでしょう。

# 後始末
sudo umount /mnt/luks-test
sudo cryptsetup close crypt-luks-test
sudo losetup -d /dev/loop0
sudo rm /var/tmp/luks-test.img
sudo rm luks-test.header rand.key