Thursday, January 5, 2023

Seedshift - steganographically encrypt your seed words

Pertaining to recent events (both the FTX collapse and LastPass hack), self-custody of your crypto and with it safe backup of your mnemonic seed words has never been more warranted I think. I keep my seed words secured in a local KeePass database in a few different places, but this guy kept them on LastPass. I wrote this script a few years ago to be able to securely write them down in an easy human-readable format and not have to worry about it even if a thief/hacker came across them: https://github.com/mifunetoshiro/Seedshift

It steganographically encrypts your English mnemonic seed words using a date shift cipher, and optionally obfuscates them by mapping them to the Unicode characters in the Traditional Chinese BIP-39 wordlist. There is also an option to split the encrypted/obfuscated seed words into 2-out-of-3 recovery sheets.

Note that you do not need this script to encrypt/decrypt your seed words with a date shift cipher, you can do all of it by hand on a piece of paper. Please read the "Safe usage" section on GitHub!

Example usage:

Let's say oppose duck hello neglect reveal key humor mosquito road evoke flock hedgehog are your seed words. The script takes dates in YYYY-MM-DD format. So let's say you use 3 dates: your mother's birthday is 1963-07-10, your father's birthday is 1956-04-27, and your birthday is 1994-01-31. The script will automatically sort the dates from oldest to newest (you don't have to input them in that order) and split each in 3 parts (year, month, day) which will be used to right-shift the words' positions in the English BIP-39 wordlist. In the above example: 1956, 4, 27, 1963, 7, 10, 1994, 1, 31. The script will shift the words and output a table with the shifted words, their number and the Unicode codepoint of the Chinese counterpart character in the Traditional Chinese wordlist (if present):

| # | Original | Number | Shifted | Encrypted | Number | Chinese | |----|----------|--------|---------|-----------|--------|---------| | 1 | oppose | 1245 | 1956 | mosquito | 1153 | 5BF6 | | 2 | duck | 543 | 4 | dust | 547 | 5B57 | | 3 | hello | 855 | 27 | hotel | 882 | 6162 | | 4 | neglect | 1185 | 1963 | maximum | 1100 | 7238 | | 5 | reveal | 1476 | 7 | rich | 1483 | 6C2E | | 6 | key | 977 | 10 | kitten | 987 | 6FC3 | | 7 | humor | 889 | 1994 | hair | 835 | 4E4E | | 8 | mosquito | 1153 | 1 | mother | 1154 | 5348 | | 9 | road | 1496 | 31 | salute | 1527 | 95CA | | 10 | evoke | 625 | 1956 | dream | 533 | 52E2 | | 11 | flock | 715 | 4 | flush | 719 | 932F | | 12 | hedgehog | 853 | 27 | hospital | 880 | 4E95 | 

To decrypt them and get back your original seed words, the script will accept the encrypted words, their numbers or the Unicode codepoints and the same dates you used to encrypt them (again, you can also do all of this by hand and don't need the script at all).

Note that the last encrypted word will most likely not be a valid checksum word (in the above example, hospital is valid, though). Having a valid checksum last word can provide plausible deniability in that the encrypted words are in fact encrypted, as they are valid BIP-39 seed words. You could even store a small amount of coins there, so if someone ever steals/uses your seed words, that's all they're going to think you have. The script can generate a valid last checksum word for your encrypted words if you want to replace it (if it's already valid, the script will tell you so, and you don't have to replace it). If you choose to replace it, you will have to remember or write down your original or encrypted last word as well, or you will optionally have to bruteforce/generate all valid checksum words with this script and test them one by one (there are 128, 64, 32 and 8 valid checksum words for 12, 15, 18 and 24 seed words, respectively)!

You can store the Chinese Unicode codepoints in multiple ways, since each is 4 characters long (just remember this fact when you want to rebuild your original seed words). You could write it unchanged: 5BF6 5B57 6162 7238 6C2E 6FC3 4E4E 5348 95CA 52E2 932F 4E95, or, to make it look even more random, as a bunch of hexadecimal characters that return useless nonsense when converted back to text ([ö[Wabr8l.oÃNNSHÊRâ/N), you could write it without spaces: 5BF65B57616272386C2E6FC34E4E534895CA52E2932F4E95, you could write it with a space every 2 characters: 5B F6 5B 57 61 62 72 38 6C 2E 6F C3 4E 4E 53 48 95 CA 52 E2 93 2F 4E 95, you could group two or more together: 5BF65B57 61627238 6C2E6FC3 4E4E5348 95CA52E2 932F4E95, etc. I included "mapping_table.txt" and "mapping_table_unicode_sorted.txt" files for manually looking up and converting the Unicode codepoints.

Optionally, the script can output a 2-out-of-3 recovery sheets like this:

| Sheet 1 | Sheet 2 | Sheet 3 | |----------------------------|----------------------------|----------------------------| | #1: mosquito / 1153 / 5BF6 | #1: mosquito / 1153 / 5BF6 | #2: dust / 547 / 5B57 | | #2: dust / 547 / 5B57 | #3: hotel / 882 / 6162 | #3: hotel / 882 / 6162 | | #4: maximum / 1100 / 7238 | #4: maximum / 1100 / 7238 | #5: rich / 1483 / 6C2E | | #5: rich / 1483 / 6C2E | #6: kitten / 987 / 6FC3 | #6: kitten / 987 / 6FC3 | | #7: hair / 835 / 4E4E | #7: hair / 835 / 4E4E | #8: mother / 1154 / 5348 | | #8: mother / 1154 / 5348 | #9: salute / 1527 / 95CA | #9: salute / 1527 / 95CA | | #10: dream / 533 / 52E2 | #10: dream / 533 / 52E2 | #11: flush / 719 / 932F | | #11: flush / 719 / 932F | #12: hospital / 880 / 4E95 | #12: hospital / 880 / 4E95 | 

Write down and store each sheet separately at a different location.


No comments:

Post a Comment