How to load LUKS passphrase from USB, falling back to keyboard?

老子叫甜甜 提交于 2019-12-02 17:42:05
Andrew

A lot of my solution is derived from the post, Using A USB Key For The LUKS Passphrase.

  1. Create a random passphrase:

    dd if=/dev/urandom bs=1 count=256 > passphrase
    
  2. Insert a USB drive. dmesg output will show the device name; assume /dev/sdd. Figure out its size:

    blockdev --getsize64 /dev/sdd
    
  3. I decided to install the passphrase at the end of the raw device, figuring it might survive any accidental use of the USB drive.

    dd if=passphrase of=/dev/sdd bs=1 seek=<size-256>
    
  4. Add the passphrase to the LUKS volume:

    cryptsetup luksAddKey /dev/sda5 passphrase
    

    This does not affect the existing hand-entered passphrase from the installer. The passphrase file can be deleted:

    rm passphrase
    
  5. Find a unique name for the USB stick, so we can identify it when present:

    ls -l /dev/disk/by-id | grep -w sdd
    

    You should see one symlink. I will call it /dev/disk/by-id/<ID>.

  6. Edit /etc/crypttab. You should see a line like:

    sdc5_crypt UUID=b9570e0f-3bd3-40b0-801f-ee20ac460207 none luks
    

    Modify it to:

    sdc5_crypt UUID=b9570e0f-3bd3-40b0-801f-ee20ac460207 /dev/disk/by-id/<ID> luks,keyscript=/bin/passphrase-from-usb
    
  7. The keyscript referred to above will need to read the passphrase from the USB device. However, it needs to do more than that. To understand how it is used, check /usr/share/initramfs-tools/scripts/local-top/cryptroot, the script that runs at boot time to unlock the root device. Note when a keyscript is set, it is simply run and the output piped to luksOpen with no other checking. There is no way to signal an error (USB drive not present) or fall back to keyboard input. If the passphrase fails, the keyscript is run again in a loop, up to some number of times; however we are not told which iteration we are on. Also, we have no control over when the keyscript is run, so we can't be sure Linux has recognized the USB drive.

    I addressed this with some hacks:

    1. Poll on the USB drive and wait 3 seconds for it to appear. This works for me, but I would love to know a better way.

    2. Create a dummy file /passphrase-from-usb-tried on first run to indicate that we have been run at least once.

    3. If we have been run at least once, or the USB drive cannot be found, run the askpass program used by cryptroot for keyboard input.

    The final script:

    #!/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: "
    

    Finally, we need to ensure that this script is available in the initramfs. Create /etc/initramfs-tools/hooks/passphrase-from-usb containing:

    #!/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
    
  8. The USB drivers were not present in my initramfs. (It appears they are by default in later versions of Debian.) I had to add them by adding to /etc/initramfs-tools/modules:

    uhci_hcd
    ehci_hcd
    usb_storage
    
  9. When all is done, update the initramfs:

    update-initramfs -u
    

It would be ideal to me if I could simply have a small USB stick containing a passphrase that will unlock the disk. Not only would that be handy for servers (where you could leave the USB stick in the server - the goal is to be able to return broken harddisks without having to worry about confidential data), it would also be great for my laptop: Insert the USB stick when booting and remove it after unlocking the cryptodisk.

I have now written a patch that will search the root dir of all devices for the file 'cryptkey.txt' and try decrypting with each line as a key. If that fails: Revert to typing in the pass phrase.

It does mean the key cannot contain \n, but that would apply to any typed in key, too. The good part is that you can use the same USB disk to store the key for multiple machines: You do not need a separate USB disk for each. So if you have a USB drive in your physical key ring, you can use the same drive for all the machines you boot when being physically close.

You add the key with:

cryptsetup luksAddKey /dev/sda5

And then put the same key as a line in a file on the USB/MMC disk called 'cryptkey.txt'. The patch is here:

https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=864647

If the USB drivers, MMC drivers or the filesystems are not present in your initramfs, you need to add them by adding to /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

When all is done, update the initramfs:

update-initramfs -u

It can be found as patch and file at: https://gitlab.com/ole.tange/tangetools/tree/master/decrypt-root-with-usb

To accompany excellent answers above please see C routines you could use to write/generate and read raw block device key. The "readkey.c" extracts key of given size from block device and "writekey.c" can generate or write existing key to raw device. The "readkey.c" once compiled can be used in custom script to extract key of known size from raw block device like so:

readkey </path/to/device> <keysize>

To see usage for "writekey", after compiled run it with no flags.
To compile just use:

gcc readkey.c -o readkey

gcc writekey.c -o writekey

I tested both on Verbatim 16GB USB 2.0 USB flash drive with custom "keyscript=" in crypttab also published below. The idea for "crypto-usb.sh" is from "debian etch" cryptsetup guide.

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

When generating the key size of the key has to be provided, generated key is saved to ".tmpckey" file with file permissions 0600 for later use. When writing existing key, size is determined by measuring the existing key size. This looks like complex approach however once compiled with simple "gcc" it can provide easy way of manipulating the raw key content.

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;
}

To verify key written to raw device is the same as the one in file(below will output nothing if keys are identical):

diff -B <(./readkey </path/to/device> 4096) <(cat .tmpckey)

Or for existing key generated using own means:

diff -B <(./readkey </path/to/device> <generated elsewhere key size>) <(cat </path/to/keyfile>)

Thank You

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!