Skip to content

Encrypted Secret Key

Encrypted secret keys are presented in a printable form with a mocryptsec0 prefix.

Secret Handling

In situations where a user could enter a password to decrypt a secret key, the secret key should only be stored in this encrypted form and the user prompted for the password to decrypt it. The decrypted secret key should be kept only in memory and as best possible zeroized before memory is released.

Within this algorithm, three pieces of data are secret and SHOULD be zeroed and handled as secret key material: PASSWORD, SYMMETRIC_KEY, SECRET_KEY, and CHECKED_SECRET_KEY.

Encryption algorithm

Inputs

SECRET_KEY = The secret key to be encrypted, as 32 bytes (not as human readable mosec0).

PASSWORD = Read from the user. The password should be unicode normalized to NFKC format to ensure that the password can be entered identically on other computers/clients. The password is temporary and SHOULD be zeroed and MUST be discarded after use and not stored or reused for any other purpose.

LOG_N_BYTE = Let the user or implementer choose one byte representing a power of 2 (e.g. 18 represents 262,144) which is used as the number of rounds for scrypt. Larger numbers take more time and more memory, and offer better protection. 18 is a reasonable default as of 2025. Implementations MAY fail if this value is greater than 22:

LOG_N MEMORY REQUIRED APPROX TIME ON FAST COMPUTER
16 64 MiB 100 ms
18 256 MiB
20 1 GiB 2 seconds
21 2 GiB
22 4 GiB

Algorithm

SALT = 16 random bytes

SYMMETRIC_KEY = scrypt(password=PASSWORD, salt=SALT, log_n=LOG_N_BYTE, r=8, p=1, dkLen=40)

The symmetric key output should be 40 bytes long (the dkLen). This symmetric encryption key is temporary and SHOULD be zeroed and MUST be discarded after use and not stored or reused for any other purpose.

RAND4 = Four random bytes

CHECKBYTES = [0xb9, 0x60, 0xa1, 0xe2]

RANDOMIZED_CHECKBYTES = xor(RAND4, CHECKBYTES)

CHECKED_SECRET_KEY = CONCAT(SECRET_KEY, RAND4, RANDOMIZED_CHECKBYTES)

This value is temporary and SHOULD be zeroed and MUST be discarded after use and not stored or reused for any other purpose.

XOR_OUTPUT = xor(SYMMETRIC_KEY, CHECKED_SECRET_KEY) rat

VERSION_NUMBER_BYTE = 0x01

CONCATENATION = concat( VERSION_NUMBER_BYTE, LOG_N_BYTE, SALT, XOR_OUTPUT )

ENCRYPTED_SECRET_KEY = CONCAT('mocryptsec0', zbase32_encode(CONCATENATION))

Decryption algorithm

Inputs

ENCRYPTED_SECRET_KEY = Read as input

PASSWORD = Read from the user. The password should be unicode normalized to NFKC format to ensure that the password can be entered identically on other computers/clients. The password is temporary and SHOULD be zeroed and MUST be discarded after use and not stored or reused for any other purpose.

Algorithm

ZBASE32_ENCODED = strip_and_check_prefix(ENCRYPTED_SECRET_KEY, "mocryptsec0")

CONCATENATION = zbase32_decode(ZBASE32_ENCODED)

VERSION_NUMBER_BYTE = CONCATENATION[0]

Check that the VERSION_NUMBER_BYTE is 0x01 else fail.

LOG_N_BYTE = CONCATENATION[1]

Consider failing if this value is greater than 22 (which is compute excessive).

SALT = CONCATENATION[2..18]

XOR_OUTPUT = CONCATENATION[18..54]

SYMMETRIC_KEY = scrypt(password=PASSWORD, salt=SALT, log_n=LOG_N_BYTE, r=8, p=1)

The symmetric key output should be 36 bytes long (the dkLen). This symmetric encryption key is temporary and SHOULD be zeroed and MUST be discarded after use and not stored or reused for any other purpose.

CHECKED_SECRET_KEY = xor(SYMMETRIC_KEY, XOR_OUTPUT)

This value is temporary and SHOULD be zero and MUST be discarded after use and not stored or reused for any other purpose.

RAND4 = CHECKED_SECRET_KEY[32..36]

RANDOMIZED_CHECKBYTES = CHECKED_SECRET_KEY[36..40]

CHECKBYTES = xor(RAND4, RANDOMIZED_CHECKBYTES)

Verify that these equal [0xb9, 0x60, 0xa1, 0xe2]. If not, presume the password was incorrect.

SECRET_KEY = CHECKED_SECRET_KEY[0..32]