要約
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の記事などを参照。
LUKSの実装であるdm-cryptはデバイスの領域を一部使って、暗号化に使う鍵情報などを持ったりして、デバイスのデータの暗号化ができる。cryptsetupはそのお助けツール。
LUKSの仕様上、パスフレーズや鍵ファイルなど複数の手段で暗号化を行っているキー(マスターキー)を取得できるようになっているっぽい。
しかし、そこの領域が壊れたら読み込めなくなるのでは?復旧できなくなってしまうのではないか?と思い調べたらバックアップができるようだったので試した。
色々やってますが、やることは luksHeaderBackupでバックアップファイルを取って、luksHeaderRestoreでバックアップファイルから復旧できることを確認しているだけです。
やってみた
$ 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ヘッダ領域を上書きする感じっぽい。単純な仕組みですね。
以上。ここまで。
バックアップはどうすれば?
パスフレーズや暗号鍵ファイルなどがわかるとマスターキーがゲットできてしまうはずなので。
おまけ: ついでにデータ領域を壊してみた
以降は、興味本位だけど、データを壊したらどうなるか試す。
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
壊れた(キャッキャ)
# 後始末 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