hack news

So I lost my OpenBSD FDE password (2016)

The opposite day I feature up a brand unique OpenBSD occasion with a pleasant RAID array, encrypted with Fleshy Disk Encryption. And promptly proceeded to fail to take note phase of the passphrase.

Every person knows things receive attention-grabbing when I lose a password.

I did a inclined try at finding some public bruteforce instrument, and found nothing. I scream inclined in consequence of someplace in the motivate of my brain, I already desired to desire a see on the OpenBSD FDE implementation.

Very itsy-bitsy is documented, and whereas I discontinuance belief OpenBSD, I must know how my files is encrypted. So this became as soon as the “finest” occasion.

Withhold on, in consequence of this is able to per chance well well also very successfully be a bumpy trek, straight into the OpenBSD core sources, following notes I took one day of the ~3 hours job.


We want to extract ample files from the encrypted disk and rebuild ample of the decryption algorithm in dispute to fleet try many passphrases.

What this generally ability in FDE is finding the predominant points of the Key Derivation Characteristic, and whatever mechanism is dilapidated to detect if the passphrase is appropriate or now not.

Starting choices

A instructed. A rattling instructed.

# bioctl -c C -l sd3a softraid0Passphrase:softraid0: incorrect key or passphrase

We begin chasing by taking a earn a look on the bioctl and softraid_crypto implementations, Cmd-F’ing “Passphrase:” and “wrong key or passphrase”.



The first hit is promising.

bio_kdf_derive(&kdfinfo, &kdfhint, "Passphrase: ", 0);
voidbio_kdf_derive(struct sr_crypto_kdfinfo *kdfinfo, struct sr_crypto_kdf_pbkdf2    *kdfhint, char* prompt, int verify)// [...]derive_key_pkcs(kdfhint->rounds,    kdfinfo->maskkey, sizeof(kdfinfo->maskkey),    kdfhint->salt, sizeof(kdfhint->salt), prompt, verify);

derive_key_pkcs is a banal checking wrapper for pkcs5_pbkdf2so we now know how the passphrase is derived into a key:

kdfinfo->maskkey=pbkdf2(password, kdfhint->salt, kdfhint->rounds)

Let’s lumber kdfhint.

Pass the salt

The salt will not be any doubt saved on the encrypted disk. The article earn to be populated by the strains accurate above the bio_kdf_derive call, in consequence of forward of that its reminiscence is zeroed:

create.bc_opaque=&kdfhint;create.bc_opaque_size=sizeof(kdfhint);create.bc_opaque_flags=BIOC_SOOUT;/* try to get KDF hint */if (ioctl(devh, BIOCCREATERAID, &create))err(1, "ioctl");

I attempted a few leads right here, along with following the BIOCCREATERAID ioctl, nonetheless what got me someplace became as soon as a code peek “bc_opaque“.

softraid_crypto.c L223-L225

if (copyout(sd->mds.mdd_crypto.scr_meta->scm_kdfhint,    bc->bc_opaque, bc->bc_opaque_size))goto done;

It is copied from some deeper metadata object. This appears complex. Hmmm.

Let’s try a brand unique perspective: what’s the receive of the kdfhint?

softraidvar.h L53-L62

/* * sr_crypto_genkdf is a generic hint for the KDF performed in userland and * is not interpreted by the kernel. */struct sr_crypto_genkdf {u_int32_tlen;u_int32_ttype;#define SR_CRYPTOKDFT_INVALID0#define SR_CRYPTOKDFT_PBKDF21#define SR_CRYPTOKDFT_KEYDISK2};/* * sr_crypto_genkdf_pbkdf2 is a hint for the PKCS#5 KDF performed in userland * and is not interpreted by the kernel. */struct sr_crypto_kdf_pbkdf2 {u_int32_tlen;u_int32_ttype;u_int32_trounds;u_int8_tsalt[128];};

Aha! If or now not it’s “now not interpreted by the kernel”, then it earn to be verbatim in the disk metadata. We want to head making an strive at one.

A straightforward example

To breed a case where we can know if we got it honest appropriate, we receive a itsy-bitsy encrypted image, with passphrase “password”.

# dd if=/dev/zero of=file.img bs=1 count=1M# vnconfig vnd0 file.img# disklabel -E /dev/rvnd0cLabel editor (enter '?' for help at any prompt)> a aoffset: [0]size: [2048]FS type: [4.2BSD] RAID> w> qNo label changes.# bioctl -c C -l /dev/vnd0a softraid0New passphrase: passwordRe-type passphrase: passwordsoftraid0: CRYPTO volume attached as sd4

Right here is the hexdump: //gist.github.com/FiloSottile/8294e708396396d6b6d49c7c839b72ec

We’re shopping for a sr_crypto_kdf_pbkdf2 construction, which we can acknowledge in consequence of it begins with a u_int32_t length, followed by a u_int32_t receive of cost 1, followed by a u_int32_t choice of rounds. There are many 01 00 00 00 (itsy-bitsy endian!) round, nonetheless handiest one appears surrounded by two other u_int32_t:

00002960  -- -- -- -- -- -- -- --  -- -- -- -- 8c 00 00 00  |..U...(zU.......|00002970  01 00 00 00 00 20 00 00  50 1f db 08 97 6d 2c 40  |..... ..P....m,@|00002980  63 fb ff 91 5e 6c 75 fc  b9 44 86 16 77 1f 6d 65  |c...^lu..D..w.me|00002990  4d 64 f8 56 ab 11 83 c7  7b 01 ac a0 f2 69 51 83  |Md.V....{....iQ.|000029a0  b3 41 df c4 83 21 7a ce  75 37 3d f8 80 4f 6d 36  |.A...!z.u7=..Om6|000029b0  06 63 55 15 ff de 7d 7a  b1 ac dd 0c f8 41 63 bb  |.cU...}z.....Ac.|000029c0  42 cc a6 85 4a b5 52 f4  50 ec 9f 05 3f 9d 8b 8d  |B...J.R.P...?...|000029d0  64 fe 85 ba 8f ce 08 87  97 e2 8d 35 2c 9d 6a 2d  |d..........5,.j-|000029e0  cb 8c e2 7e 72 65 7d 7e  56 76 87 89 e6 ba cc 49  |...~re}~Vv.....I|000029f0  bd 84 43 ef e6 3e 07 d6  00 00 00 00 00 00 00 00  |..C..>..........|

Indeed, the length field is 8c=140=4 + 4 + 4 + 128and the rounds quantity 0x2000 in all equity priced. We earn our salt!

A checksum to ascertain your key

While lurking this comment caught my discover about:

/* Check that the key decrypted properly. */sr_crypto_calculate_check_hmac_sha1(sd->mds.mdd_crypto.scr_maskkey,    sizeof(sd->mds.mdd_crypto.scr_maskkey),    (u_int8_t *)sd->mds.mdd_crypto.scr_key,    sizeof(sd->mds.mdd_crypto.scr_key),    check_digest);if (memcmp(sd->mds.mdd_crypto.scr_meta->chk_hmac_sha1.sch_mac,    check_digest, sizeof(check_digest)) !=0) {...}

Interestingly the correctness of the passphrase is checked by doing a HMAC of one thing, and evaluating it with an anticipated cost.

Let’s undercover agent what this chk_hmac_sha1 construction is.

/* * Check that HMAC-SHA1_k(decrypted scm_key)==sch_mac, where * k=SHA1(masking key) */struct sr_crypto_chk_hmac_sha1 {u_int8_tsch_mac[20];} __packed;

Oh, thanks, that makes things powerful more uncomplicated. What the comment calls “decrypted scm_key” is named scr_key in the snippet above.

We earn our take a look at algorithm:

HMAC-SHA1(k=SHA1(maskkey), scr_key)==sch_mac

Keys, keys that encrypt keys

Let’s undercover agent how this scr_key is decrypted. Good above.

if (sr_crypto_decrypt((u_char *)sd->mds.mdd_crypto.scr_meta->scm_key,    (u_char *)sd->mds.mdd_crypto.scr_key,    sd->mds.mdd_crypto.scr_maskkey, sizeof(sd->mds.mdd_crypto.scr_key),    sd->mds.mdd_crypto.scr_meta->scm_mask_alg)==-1)goto out;

sr_crypto_decrypt is accurate AES-ECB-256. So final portion of the algorithm:

scr_key=AES-ECB-256_decrypt(k=maskkey, scm_key)

Hexdump spelunking

Now, or now not it’s a topic of finding scm_key and sch_mac in the disk image. All all over again, let’s earn a look on the guidelines structures, starting with chk_hmac_sha1.

u_int32_tscm_check_alg;/* key chksum algorithm */#define SR_CRYPTOC_HMAC_SHA11u_int32_tscm_pad2;union {struct sr_crypto_chk_hmac_sha1chk_hmac_sha1;u_int8_tchk_reserved2[64];}_scm_chk;

Sweet. We’re shopping for 01 00 00 00 (scm_check_alg), followed by 00 00 00 00 (scm_pad2), followed by 20 random bytes (SHA1). Certain ample, accurate after the salt, there may be our take a look at HMAC:

00002a60  00 00 00 00 00 00 00 00  00 00 00 00 01 00 00 00  |................|00002a70  00 00 00 00 26 e8 25 6f  86 8f cd 33 88 1c d4 f1  |....&.%o...3....|00002a80  1e 9d 2a 98 ca 21 2d 9c  00 00 00 00 00 00 00 00  |..*..!-.........|00002a90  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|

Finally, we’d like to receive the encrypted key, scm_key. This took me a whereas, till I noticed the dimensions of this encrypted blob:

#define SR_CRYPTO_MAXKEYS32/* max keys per volume */#define SR_CRYPTO_KEYBITS512/* AES-XTS with 2 * 256 bit keys */#define SR_CRYPTO_KEYBYTES(SR_CRYPTO_KEYBITS>> 3)u_int8_tscr_key[SR_CRYPTO_MAXKEYS][SR_CRYPTO_KEYBYTES];/* symmetric keys used for disk encryption */u_int8_tscm_key[SR_CRYPTO_MAXKEYS][SR_CRYPTO_KEYBYTES];

32 * 512/8=2048=0x8000x800 bytes of random stuff. You cannot truly proceed out it in the hexdump. But where are the boundaries? Successfully, if we’re fortunate, the line where the massive random blob begins (00002160) and the one where the salt begins (00002960) will likely be roughly… Certain! Exactly 0x800 bytes apart 🙂

That random blob is all key enviornment clothfollowed by the PBKDF2 rounds and salt, and by the take a look at HMAC.

Wrapping it up

So now we found the total pieces to write some code and discover if our assumptions had been appropriate:

func main() {    scmKey :=decode(scmKey)    salt :=decode(salt)    maskkey :=pbkdf2.Key([]byte("password"), salt, rounds, 32, sha1.New)    // AES-ECB-256_decrypt(k=maskkey, scm_key)=scr_key    a, err :=aes.NewCipher(maskkey)    if err !=nil {        log.Fatal(err)    }    for i :=0; i 

If we’re honest appropriate, this is able to per chance well well also output the identical HMAC as in the final hexdump snippet. The first time I forgot to hash the maskkeynearly tore my hair out. But then…

$ go build -i . && ./openbsd-fde-crack00000000  26 e8 25 6f 86 8f cd 33  88 1c d4 f1 1e 9d 2a 98  |&.%o...3......*.|00000010  ca 21 2d 9c                                       |.!-.|


Now that all people knows the remark technique to extract the guidelines and the remark technique to try passphrases in opposition to it, this is able to per chance well well also very successfully be trivial to write a bruteforce instrument to receive successfully the phase of passphrase I forgot.

There is some code right here, nonetheless invent now not set an scream to a fireplace-and-fail to take note instrument, this submit presents you ample files to identify stuff on your have: //github.com/FiloSottile/openbsd-fde-crack

To understand what happens the subsequent time I lose a password (remark), follow me on Twitter.

UPDATE: I found it! After fixing a computer virus or two in the brute force instrument and nearly shedding hope, it found the coolest mixture of forgotten notice and (Italian) misspelling.

UPDATE: I later found a pleasant article documenting the total system. It also entails references to JohnTheRipper having a module for this. Successfully, this became as soon as extra stress-free.

Related Articles

Leave a Reply

Your email address will not be published. Required fields are marked *

Check Also
Back to top button