{"id":"f8204009179be1f0a880b35fb72cd18fc65f3d7540b5ff31245286fc0d9e7a7e","pubkey":"2b39b4ffe62933df970e19366c22c1e092f953f83fcfed754e0f04d5a3b459f9","created_at":1780437255,"kind":30817,"tags":[["d","nip-49"],["title","NIP-49: Private Key Encryption (`ncryptsec`)"],["summary","ncryptsec, a private key encrypted under a password, with scrypt deciding how expensive guessing it is."],["s","draft"],["t","nostr"],["t","nip"],["alt","A specification: NIP-49: Private Key Encryption (`ncryptsec`)"],["client","openspecs-import"],["published_at","1706543116"],["proxy","https://github.com/nostr-protocol/nips/blob/656cecc7c0a815b6a2b218d3b5d6f078b3f4dbab/49.md","web"],["x","877c17c9b2d5083d4301c9f284c9dce51190d4a4ab648aee1af4a42db864d34a"]],"content":"\nNIP-49\n======\n\nPrivate Key Encryption (`ncryptsec`)\n------------------------------------\n\n`draft` `optional`\n\nThis NIP defines a method by which clients can encrypt (and decrypt) a user's private key with a password.\n\nSymmetric Encryption Key derivation\n-----------------------------------\n\nPASSWORD = 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.\n\nLOG\\_N = 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:\n\n    | LOG_N | MEMORY REQUIRED | APPROX TIME ON FAST COMPUTER |\n    |-------|-----------------|----------------------------- |\n    | 16    | 64 MiB          | 100 ms                       |\n    | 18    | 256 MiB         |                              |\n    | 20    | 1 GiB           | 2 seconds                    |\n    | 21    | 2 GiB           |                              |\n    | 22    | 4 GiB           |                              |\n\nSALT = 16 random bytes\n\nSYMMETRIC_KEY = scrypt(password=PASSWORD, salt=SALT, log\\_n=LOG\\_N, r=8, p=1)\n\nThe symmetric key should be 32 bytes long.\n\nThis symmetric encryption key is temporary and should be zeroed and discarded after use and not stored or reused for any other purpose.\n\n\nEncrypting a private key\n------------------------\n\nThe private key encryption process is as follows:\n\nPRIVATE\\_KEY = User's private (secret) secp256k1 key as 32 raw bytes (not hex or bech32 encoded!)\n\nKEY\\_SECURITY\\_BYTE = one of:\n\n*  0x00 - if the key has been known to have been handled insecurely (stored unencrypted, cut and paste unencrypted, etc)\n*  0x01 - if the key has NOT been known to have been handled insecurely (stored unencrypted, cut and paste unencrypted, etc)\n * 0x02 - if the client does not track this data\n\nASSOCIATED\\_DATA = KEY\\_SECURITY\\_BYTE\n\nNONCE = 24 byte random nonce\n\nCIPHERTEXT = XChaCha20-Poly1305(\n    plaintext=PRIVATE\\_KEY,\n    associated_data=ASSOCIATED\\_DATA,\n    nonce=NONCE,\n    key=SYMMETRIC\\_KEY\n)\n\nVERSION\\_NUMBER = 0x02\n\nCIPHERTEXT_CONCATENATION = concat(\n    VERSION\\_NUMBER,\n    LOG\\_N,\n    SALT,\n    NONCE,\n    ASSOCIATED\\_DATA,\n    CIPHERTEXT\n)\n\nENCRYPTED\\_PRIVATE\\_KEY = bech32_encode('ncryptsec', CIPHERTEXT\\_CONCATENATION)\n\nThe output prior to bech32 encoding should be 91 bytes long.\n\nThe decryption process operates in the reverse.\n\n\nTest Data\n---------\n\n## Password Unicode Normalization\n\nThe following password input: \"ÅΩẛ̣\"\n- Unicode Codepoints: U+212B U+2126 U+1E9B U+0323\n- UTF-8 bytes: [0xE2, 0x84, 0xAB, 0xE2, 0x84, 0xA6, 0xE1, 0xBA, 0x9B, 0xCC, 0xA3]\n\nShould be converted into the unicode normalized NFKC format prior to use in scrypt: \"ÅΩẛ̣\"\n- Unicode Codepoints: U+00C5 U+03A9 U+1E69\n- UTF-8 bytes: [0xC3, 0x85, 0xCE, 0xA9, 0xE1, 0xB9, 0xA9]\n\n## Encryption\n\nThe encryption process is non-deterministic due to the random nonce.\n\n## Decryption\n\nThe following encrypted private key:\n\n`ncryptsec1qgg9947rlpvqu76pj5ecreduf9jxhselq2nae2kghhvd5g7dgjtcxfqtd67p9m0w57lspw8gsq6yphnm8623nsl8xn9j4jdzz84zm3frztj3z7s35vpzmqf6ksu8r89qk5z2zxfmu5gv8th8wclt0h4p`\n\nWhen decrypted with password='nostr' and log_n=16 yields the following hex-encoded private key:\n\n`3501454135014541350145413501453fefb02227e449e57cf4d3a3ce05378683`\n\nDiscussion\n----------\n\n### On Key Derivation\n\nPasswords make poor cryptographic keys. Prior to use as a cryptographic key, two things need to happen:\n\n1. An encryption key needs to be deterministically created from the password such that is has a uniform functionally random distribution of bits, such that the symmetric encryption algorithm's assumptions are valid, and\n2. A slow irreversible algorithm should be injected into the process, so that brute-force attempts to decrypt by trying many passwords are severely hampered.\n\nThese are achieved using a password-based key derivation function. We use scrypt, which has been proven to be maximally memory hard and which several cryptographers have indicated to the author is better than argon2 even though argon2 won a competition in 2015.\n\n### On the symmetric encryption algorithm\n\nXChaCha20-Poly1305 is typically favored by cryptographers over AES and is less associated with the U.S. government.  It (or it's earlier variant without the 'X') is gaining wide usage, is used in TLS and OpenSSH, and is available in most modern crypto libraries.\n\nRecommendations\n---------\n\nIt is not recommended that users publish these encrypted private keys to nostr, as cracking a key may become easier when an attacker can amass many encrypted private keys.\n\nIt is recommended that clients zero out the memory of passwords and private keys before freeing that memory.\n","sig":"b5944307dd8fe3fa5a40a5c191c413bbee7be707c5c0e62c1a06079a5961a5983a4e998bb1d745df813862e0bc2ef78f6982b42f9b945e84eaf4d7b48c8100cc"}