USBドライブを使用するか、キーボードでパスフレーズを入力してディスクのロックを解除できる、ディスク全体を暗号化したヘッドレスLinux(Debian Wheezy)PCをセットアップしたいと思います。私の出発点は、Debianインストーラの基本的な全ディスク暗号化オプションを使用した新規インストールです。これは/ boot以外のすべてをLUKS暗号化論理ボリュームグループとして管理し、キーボードオプションを提供します。私の現在の解決策を回答で説明します。それが有用であり、他の人がそれを改善できることを願っています。
これが私が抱えていた問題のいくつかです:
パスフレーズを設定してUSBドライブに保存します。
USBモジュールを時間内にロードします。
USBドライブがLinuxによって認識されるのを待ってから、読み取りを試みます。
正しいUSBドライブを特定する(たまたま挿入された他のドライブではない)。
USBドライブからパスフレーズを引き出すための「キースクリプト」を書く。
すべてのUSB障害の場合に、キーボードへのフォールバックが確実に開始されるようにします。
大幅に改善された回答を受け入れ、貢献を提供する回答に賛成します。
私の解決策の多くは、投稿から派生しています LUKSパスフレーズにUSBキーを使用 。
ランダムなパスフレーズを作成します。
dd if=/dev/urandom bs=1 count=256 > passphrase
USBドライブを挿入します。 dmesg
出力にはデバイス名が表示されます。 /dev/sdd
を想定します。そのサイズを把握します。
blockdev --getsize64 /dev/sdd
USBドライブを誤って使用しても耐えられると考えて、rawデバイスの最後にパスフレーズをインストールすることにしました。
dd if=passphrase of=/dev/sdd bs=1 seek=<size-256>
LUKSボリュームにパスフレーズを追加します。
cryptsetup luksAddKey /dev/sda5 passphrase
これは、インストーラーからの既存の手入力パスフレーズには影響しません。パスフレーズファイルは削除できます。
rm passphrase
USBスティックの一意の名前を見つけて、存在する場合に識別できるようにします。
ls -l /dev/disk/by-id | grep -w sdd
1つのシンボリックリンクが表示されます。私はそれを/dev/disk/by-id/<ID>
と呼びます。
/etc/crypttab
を編集します。次のような行が表示されます。
sdc5_crypt UUID=b9570e0f-3bd3-40b0-801f-ee20ac460207 none luks
次のように変更します。
sdc5_crypt UUID=b9570e0f-3bd3-40b0-801f-ee20ac460207 /dev/disk/by-id/<ID> luks,keyscript=/bin/passphrase-from-usb
上記のkeyscript
は、USBデバイスからパスフレーズを読み取る必要があります。ただし、それ以上のことを行う必要があります。使用方法を理解するには、ブート時に実行されてルートデバイスのロックを解除するスクリプトである/usr/share/initramfs-tools/scripts/local-top/cryptroot
を確認してください。 keyscript
が設定されている場合、それは単に実行され、出力は他のチェックなしでluksOpen
にパイプされることに注意してください。エラー(USBドライブが存在しない)を通知したり、キーボード入力にフォールバックしたりする方法はありません。パスフレーズが失敗した場合、キースクリプトはループ内で最大数回実行されます。ただし、どの反復を行っているかはわかりません。また、キースクリプトがいつ実行されるかを制御できないため、LinuxがUSBドライブを認識したかどうかを確認できません。
私はいくつかのハックでこれに対処しました:
USBドライブをポーリングし、表示されるまで3秒待ちます。これは私にとってはうまくいきますが、もっと良い方法を知りたいです。
最初の実行時にダミーファイル/passphrase-from-usb-tried
を作成して、少なくとも1回実行されたことを示します。
少なくとも1回実行したことがある場合、またはUSBドライブが見つからない場合は、キーボード入力にaskpass
が使用するcryptroot
プログラムを実行します。
最終的なスクリプト:
#!/bin/sh
set -e
if ! [ -e /passphrase-from-usb-tried ]; then
touch /passphrase-from-usb-tried
if ! [ -e "$CRYPTTAB_KEY" ]; then
echo "Waiting for USB stick to be recognized..." >&2
sleep 3
fi
if [ -e "$CRYPTTAB_KEY" ]; then
echo "Unlocking the disk $CRYPTTAB_SOURCE ($CRYPTTAB_NAME) from USB key" >&2
dd if="$CRYPTTAB_KEY" bs=1 skip=129498880 count=256 2>/dev/null
exit
else
echo "Can't find $CRYPTTAB_KEY; USB stick not present?" >&2
fi
fi
/lib/cryptsetup/askpass "Unlocking the disk $CRYPTTAB_SOURCE ($CRYPTTAB_NAME)\nEnter passphrase: "
最後に、このスクリプトがinitramfsで使用可能であることを確認する必要があります。以下を含む/etc/initramfs-tools/hooks/passphrase-from-usb
を作成します。
#!/bin/sh
PREREQ=""
prereqs() {
echo "$PREREQ"
}
case "$1" in
prereqs)
prereqs
exit 0
;;
esac
. "${CONFDIR}/initramfs.conf"
. /usr/share/initramfs-tools/hook-functions
copy_exec /bin/passphrase-from-usb /bin
USBドライバーが私のinitramfsに存在しませんでした。 (Debianの新しいバージョンではデフォルトであるようです。)/etc/initramfs-tools/modules
に追加して追加する必要がありました。
uhci_hcd
ehci_hcd
usb_storage
すべて完了したら、initramfsを更新します。
update-initramfs -u
ディスクのロックを解除するパスフレーズを含む小さなUSBスティックを手に入れることができれば理想的です。それはサーバーにとって便利であるだけでなく(サーバーにUSBスティックを残すことができます-目標は機密データを心配することなく壊れたハードディスクを返すことができることです)、それは私のラップトップにも最適です:挿入起動時にUSBスティックを使用し、暗号ディスクのロックを解除してから取り外します。
これで、すべてのデバイスのルートディレクトリでファイル「cryptkey.txt」を検索し、各行をキーとして復号化を試みるパッチを作成しました。それが失敗した場合:パスフレーズの入力に戻ります。
これは、キーに\ nを含めることができないことを意味しますが、これは入力されたキーにも当てはまります。良い点は、同じUSBディスクを使用して複数のマシンのキーを保存できることです。それぞれに個別のUSBディスクは必要ありません。したがって、物理キーリングにUSBドライブがある場合は、物理的に近いときに起動するすべてのマシンに同じドライブを使用できます。
次のコマンドでキーを追加します。
cryptsetup luksAddKey /dev/sda5
次に、「cryptkey.txt」という名前のUSB/MMCディスク上のファイルの行と同じキーを配置します。パッチはここにあります:
https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=864647
USBドライバー、MMCドライバーまたはファイルシステムがinitramfsに存在しない場合は、/ etc/initramfs-tools/modulesに追加して追加する必要があります。
uhci_hcd
ehci_hcd
usb_storage
nls_utf8
nls_cp437
vfat
fat
sd_mod
mmc_block
tifm_sd
tifm_core
mmc_core
tifm_7xx1
sdhci
sdhci_pci
すべて完了したら、initramfsを更新します。
update-initramfs -u
パッチとファイルとして次の場所にあります: https://gitlab.com/ole.tange/tangetools/tree/master/decrypt-root-with-usb
以前のバージョンで機能する@Andrewからの素晴らしい回答にもかかわらず。ソリューションは実際には時代遅れであり、ubuntu18.04と19.10のために多くの調整が必要です。それで、私はこれに関する私の研究を共有したいと思います。
Crypttabにはいくつかの落とし穴があります。 sepcsは実際には14.04から18.04そして19.10に大きく変化しました。 cryptsetupのより多くのパラメーターのサポートを開始します。たとえば、keyfile-offset、keyfile-sizeなど。いくつかのオプション。 nobootwaitはなくなりました。一部のパラメーターはすでに他のディストリビューションでサポートされていますが、ubuntuではまだサポートされていません(たとえば、非常に優れたパラメーターkeyfile-timeout。これにより、keyfile-timeout後にキーボード入力に自動的にフォールバックするため、キースクリプト全体を削除できます)。
Ubuntuのcrypttabの主な落とし穴は、実際には2つの異なるプロセスで処理されることです。 1つは伝統的なinitramfsで、もう1つは最新のsystemdです。 Systemdは、多くの面でより高度で柔軟性があると考えられています。ただし、systemdはcrypptabのサポートが不十分であり、keyscriptなどの多くのオプションが黙って無視されます。なので、何が起こっているのかわかりません。発見 この投稿 。 crypttab設定に関するオンラインのほとんどすべての投稿は、systemd用ではなくinitramfs用です。したがって、問題を回避するために、crypttabのすべてのエントリにinitramfs
を追加する必要があります。
また、VMまたは繰り返し再起動せずに、keyscriptとcrypttabをデバッグするための優れた方法を発見しました。これはcryptdisks_start
です。実際に変更をinitramfsに伝達する前に、常にこのNiceコマンド。それ以外の場合は、システムからロックアウトする必要があり、chroot環境を介してのみ回復できます。
@andrewは、ファイルシステムの未加工領域でデータ非表示を使用するための優れた方法を投稿しました。ただし、パーティションを自動的に作成し、生データを多くのusbkeyに追加する場合は、非常に煩わしいことがわかりました。すべての異なるファイルシステムと異なるパーティションサイズのオフセットを計算する必要があります。さらに、ユーザーが誤ってFSに書き込んだ場合、キーが上書きされるリスクがあります。この場合、FSがないrawパーティションの方が理にかなっています。ただし、rawパーティションには自動ロック解除にあまり役立たないUUIDがないため、使用する方法を紹介します。 usbkeyファイルシステム上の通常のパスフレーズファイルpassdevの主な問題は、ファイルの読み取り中にシーク/停止しないことです。したがって、必要なときにkeyfile-offsetおよびkeyfile-sizeオプションを使用できません。キーボード入力にフォールバックします。cryptsetupは実際には入力コンテンツをスキップしようとし、コンテンツがkeyfile-sizeより短い場合、エラーが発生します。これは、オフセットが大きい場合も意味します。 、passdevは常に最初から読み取るため、非常に遅くなる可能性があります。ただし、ファイルシステム上の実際のファイルにオフセットとキーファイルサイズを実装する意味はありません。これらはrawデバイス用に作成されていると思います。
Crypttab
luks-part UUID="<uuid>" /dev/disk/by-uuid/<keyfile FS uuid>:/<keyfile path relative to usbkey root>:<timeout in sec> luks,keyfile-offset=<seek to the key>,keyfile-size=<>,keyscript=/bin/passphrase-from-usbfs.sh,tries=<number of times to try>,initramfs
keyscript passphrase-from-usbfs.shは、/lib/cryptsetup/scripts/passdev
を利用しました。これは、usbデバイスを待機し、fsをマウントしてから、ファイルの内容をパイプアウトします。 CRYPTTAB_KEY
の形式で/device-path/<keyfile FS uuid>:/<keyfile path relative to usbkey root>:<timeout in sec>
をサポートします。
#!/bin/sh
#all message need to echo to stderr, the stdout is used for passphrase
# TODO: we may need to do something about the plymouth
echo "CRYPTTAB_KEY=$CRYPTTAB_KEY" >&2
echo "CRYPTTAB_OPTION_keyfile_offset=$CRYPTTAB_OPTION_keyfile_offset" >&2
#set your offset and file size here if your system does not support those paramters
#CRYPTTAB_OPTION_keyfile_offset=
#CRYPTTAB_OPTION_keyfile_size=
echo "timeout=$CRYPTTAB_OPTION_keyfile_timeout" >&2
CRYPTTAB_OPTION_keyfile_timeout=10 # keyfile-timeout is not supported yet
pass=$(/lib/cryptsetup/scripts/passdev $CRYPTTAB_KEY)
rc=$?
if ! [ $rc -eq 0 ]; then
echo "Can't find $CRYPTTAB_KEY; USB stick not present?" >&2
/lib/cryptsetup/askpass "Unlocking the disk $CRYPTTAB_SOURCE ($CRYPTTAB_NAME) Enter passphrase: "
else
echo "successfully load passphrase." >&2
echo -n $pass
fi
フックはupdate-initramfsにスクリプトをコピーするように指示します。
#!/bin/sh
PREREQ=""
prereqs() {
echo "$PREREQ"
}
case "$1" in
prereqs)
prereqs
exit 0
;;
esac
. "${CONFDIR}/initramfs.conf"
. /usr/share/initramfs-tools/hook-functions
copy_exec /bin/passphrase-from-usbfs.sh
copy_exec /bin/passphrase-from-usb.sh
#when using passdev we need to hook additionaly FS and binary
copy_exec /lib/cryptsetup/scripts/passdev
manual_add_modules ext4 ext3 ext2 vfat btrfs reiserfs xfs jfs ntfs iso9660 udf
最後に、crypttabの新しいパラメーターを使用できるpassphrase-from-usb.shの更新バージョンを投稿しました。
上記の優れた回答を伴うには、rawブロックデバイスキーの書き込み/生成および読み取りに使用できるCルーチンを参照してください。 「readkey.c」はブロックデバイスから指定されたサイズのキーを抽出し、「writekey.c」は既存のキーを生成したり、rawデバイスに書き込んだりできます。コンパイルされた「readkey.c」をカスタムスクリプトで使用して、次のようにrawブロックデバイスから既知のサイズのキーを抽出できます。
readkey </path/to/device> <keysize>
「writekey」の使用法を確認するには、コンパイル後、フラグなしで実行します。
コンパイルするには、次を使用します。
gcc readkey.c -o readkey
gcc writekey.c -o writekey
Verbatim 16GB USB 2.0 USBフラッシュドライブで両方をテストしました。crypttabのカスタム「keyscript =」も以下に公開されています。 「crypto-usb.sh」のアイデアは、「debianetch」cryptsetupガイドからのものです。
crypto-usb.sh:
#!/bin/sh
echo ">>> Trying to get the key from agreed space <<<" >&2
modprobe usb-storage >/dev/null 2>&1
sleep 4
OPENED=0
disk="/sys/block/sdb"
boot_dir="/boot"
readkey="/boot/key/readkey"
echo ">>> Trying device: $disk <<<" >&2
F=$disk/dev
if [ 0`cat $disk/removable` -eq 1 -a -f $F ]; then
mkdir -p $boot_dir
mount /dev/sda1 $boot_dir -t ext2 >&2
echo ">>> Attempting key extraction <<<" >&2
if [ -f $readkey ]; then
# prints key array to the caller
$readkey /dev/sdb 4096
OPENED=1
fi
umount $boot_dir >&2
fi
if [ $OPENED -eq 0 ]; then
echo "!!! FAILED to find suitable key !!!" >&2
echo -n ">>> Try to enter your password: " >&2
read -s -r A
echo -n "$A"
else
echo ">>> Success loading key <<<" >&2
fi
キーのキーサイズを生成する必要がある場合、生成されたキーは、後で使用するためにファイル権限0600で「.tmpckey」ファイルに保存されます。既存の鍵を書き込む場合、サイズは既存の鍵のサイズを測定することによって決定されます。これは複雑なアプローチのように見えますが、単純な「gcc」でコンパイルすると、生のキーコンテンツを操作する簡単な方法を提供できます。
readkey.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void main(int argc, char *argv[])
{
int blockSize = 512;
int keySize = 2048;
FILE *device;
if ( argc == 3
&& (sizeof(argv[1]) / sizeof(char)) > 1
&& (sizeof(argv[2]) / sizeof(char)) > 1
&& (atoi(argv[2]) % 512) == 0
) {
device = fopen(argv[1], "r");
if(device == NULL) {
printf("\nI got trouble opening the device %s\n", argv[1]);
exit(EXIT_FAILURE);
}
keySize = atoi(argv[2]);
}
else if ( argc == 2
&& (sizeof(argv[1]) / sizeof(char)) > 1
) {
device = fopen(argv[1], "r");
if(device == NULL) {
printf("\nI got trouble opening the device %s\n", argv[1]);
exit(EXIT_FAILURE);
}
}
else {
printf("\nUsage: \n");
printf("\nKey Size Provided: \n");
printf("\n\t\treadkey </path/to/device> <keysize> \n");
printf("\nDefault key size: %d\n", keySize);
printf("\n\t\treadkey </path/to/device>\n");
exit(1);
}
int count;
char *block;
/* Verify if key is multiple of blocks */
int numBlocks = 0;
if (keySize % 512 != 0) {
printf("\nSory but key size is not multiple of block size, try again. TA.\n");
exit(1);
}
/* Seek till the end to get disk size and position to start */
fseek(device, 0, SEEK_END);
/* Determine where is the end */
long endOfDisk = ftell(device);
/* Make sure we start again */
rewind(device); // Do I need it ???
/* Get the required amount minus block size */
long startFrom = endOfDisk - blockSize - keySize;
/* Allocate space for bloc */
block = calloc(keySize, sizeof(char));
/* Start reading from specified block */
fseek(device, startFrom, SEEK_SET);
fread(block, 1, keySize, device);
/* Do something with the data */
for(count = 0; count < keySize/*sizeof(block)*/; count++){
printf("%c", block[count]);
}
/* Close file */
fclose(device);
/* Make sure freed array is zeroed */
memset(block, 0, keySize);
free(block);
}
writekey.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[])
{
int blockSize = 512;
int keySize = 2048;
int count;
unsigned char *block;
/*
Thing to always remember that argv starts from 0 - the name of the program, and argc starts from 1 i.e. 1 is the name of the program.
*/
if ( argc == 3
&& strcmp(argv[1], "genwrite") != 0
&& (sizeof(argv[2]) / sizeof(char)) > 2
) {
char ch;
FILE *keyF;
keyF = fopen(argv[1], "r");
if (keyF == NULL) exit(EXIT_FAILURE);
/* Tell key Size */
fseek(keyF, 0, SEEK_END);
keySize = ftell(keyF);
rewind(keyF);
printf("\nKey Size: %d\n", keySize);
block = calloc(keySize, sizeof(char));
printf("\n-- Start Key --:\n");
for(count = 0; count < keySize/*sizeof(block)*/; count++){
char ch = fgetc(keyF);
block[count] = ch;
/*
Uncomment below to see your key on screen
*/
// printf("%c",ch);
}
printf("\n-- End Key --:\n");
fclose(keyF);
}
else if ( argc == 3
&& strcmp(argv[1], "genwrite") == 0
&& (sizeof(argv[2]) / sizeof(char)) > 2
)
{
printf("\n-- Attempting to create random key(ish --) of size: %d\n", keySize);
block = calloc(keySize, sizeof(char));
int count;
for(count = 0; count < keySize/*sizeof(block)*/; count++){
block[count] = (char) Rand();
}
FILE *tmpfile;
tmpfile = fopen(".tmpckey", "w");
if(tmpfile == NULL) exit(EXIT_FAILURE);
fwrite(block, 1, keySize, tmpfile);
fclose(tmpfile);
chmod(".tmpckey", 0600);
}
else if ( argc == 4
&& strcmp(argv[1], "genwrite") == 0
&& (sizeof(argv[2]) / sizeof(char)) > 2
&& ((atoi(argv[3]) % 512) == 0)
)
{
keySize = atoi(argv[3]);
printf("\n-- Attempting to create random key(ish --) of size: %d\n", keySize);
block = calloc(keySize, sizeof(char));
int count;
for(count = 0; count < keySize/*sizeof(block)*/; count++){
block[count] = (char) Rand();
}
FILE *tmpfile;
tmpfile = fopen(".tmpckey", "w");
if(tmpfile == NULL) exit(EXIT_FAILURE);
fwrite(block, 1, keySize, tmpfile);
fclose(tmpfile);
chmod(".tmpckey", 0600);
}
else {
printf("\n");
printf("################################################################################\n");
printf("# #\n");
printf("# Usage: #\n");
printf("# #\n");
printf("################################################################################\n");
printf("#> To write existing key to device: #\n");
printf("# #\n");
printf("# writekey </path/to/keyfile> </path/to/removable/sd*> #\n");
printf("# #\n");
printf("#> To generate and write pseudo random key, #\n");
printf("#> key will be saved to temporary file .tmpckey #\n");
printf("# #\n");
printf("# writekey genwrite </path/to/removable/sd*> <keysize in multiples of 512> #\n");
printf("# #\n");
printf("#> When keysize is not provided default size is set to %d. #\n", keySize);
printf("# #\n");
printf("################################################################################\n");
exit(1);
}
/*
Some printf debugging below, uncomment when needed to see what is going on.
*/
/*
printf("\nNumber of Args: %d\n", argc);
printf("\nCurrently block array contains: \n");
for(count = 0; count < keySize; count++){
printf("%c", block[count]);
}
printf("\n-- End block -- \n");
*/
/* Open Device itp... */
FILE *device = fopen(argv[2], "a");
if(device == NULL) exit(EXIT_FAILURE);
printf("\nDevice to write: %s\n", argv[2]);
fseek(device, 0, SEEK_END);
/* Determine where is the end */
long endOfDisk = ftell(device);
printf("\nDevice Size: %ld\n", endOfDisk);
/* Verify if key is multiple of blocks */
int numBlocks = 0;
if (keySize % 512 != 0 || endOfDisk < (blockSize + keySize) ) {
printf("\nSorry but key size is not multiple of block size or device you trying to write to is too small, try again. TA.\n");
fclose(device);
exit(1);
}
/* Make sure we start again */
rewind(device);
/* Get the required amount sunbstracting block size */
long startFrom = endOfDisk - blockSize - keySize;
/* Write some data to the disk */
printf("\nWriting data starting from: %ld\n", startFrom);
fseek(device, startFrom, SEEK_SET);
fwrite(block, 1, keySize, device);
printf("\nBlock Position after data write procedure : %ld\n", ftell(device));
/*
Below is just for convenience, to read what was written,
can aid in debugging hence left commented for later.
*/
/*
printf("\nAmount of Data written : %ld\n", ftell(device) - startFrom);
// Start reading from specified block
printf("\n>>>>>>>> DEBUGGING SECTION <<<<<<<<<\n");
rewind(device); //
fseek(device, startFrom, SEEK_SET);
printf("\nBlock Position before read attempted: %d\n", ftell(device));
printf("\nKey size: %d\n", keySize);
fread(block, 1, keySize, device);
// Do something with the data
printf("\nBlock Position startFrom: %ld\n", startFrom);
printf("\nBlock Position after read: %d\n", ftell(device));
printf("\n-- Buffer Read: --\n");
for(count = 0; count < keySize; count++){
printf("%c", block[count]);
}
printf("\n-- End block -- \n");
printf("\n-- -- \n");
printf("\n-- -- \n");
*/
/* Close file */
fclose(device);
/* Make sure freed array is zeroed */
memset(block, 0, keySize);
free(block);
/* Return success, might change it to be useful return not place holder */
return 0;
}
Rawデバイスに書き込まれたキーがファイル内のキーと同じであることを確認するには(キーが同一の場合、以下では何も出力されません)。
diff -B <(./readkey </path/to/device> 4096) <(cat .tmpckey)
または、独自の手段を使用して生成された既存のキーの場合:
diff -B <(./readkey </path/to/device> <generated elsewhere key size>) <(cat </path/to/keyfile>)
ありがとうございました