{"record":{"id":"d00fa5c5ec69ba90","repo":"slackhq/nebula","slug":"salt-must-be-at-least-128-bits","errorCode":null,"errorMessage":"salt must be at least 128  bits","messagePattern":"salt must be at least 128  bits","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cert/crypto.go","lineNumber":137,"sourceCode":"\t// keySize of 32 bytes will result in AES-256 encryption\n\tkey, err := deriveKey(passphrase, 32, params)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn key, nil\n}\n\n// Derives a key from a passphrase using Argon2id\nfunc deriveKey(passphrase []byte, keySize uint32, params *Argon2Parameters) ([]byte, error) {\n\tif params.version != argon2.Version {\n\t\treturn nil, fmt.Errorf(\"incompatible Argon2 version: %d\", params.version)\n\t}\n\n\tif params.salt == nil {\n\t\treturn nil, fmt.Errorf(\"salt must be set in argon2Parameters\")\n\t} else if len(params.salt) < 16 {\n\t\treturn nil, fmt.Errorf(\"salt must be at least 128  bits\")\n\t}\n\n\tkey := argon2.IDKey(passphrase, params.salt, params.Iterations, params.Memory, params.Parallelism, keySize)\n\n\treturn key, nil\n}\n\n// Prepends nonce to ciphertext\nfunc joinNonceCiphertext(nonce []byte, ciphertext []byte) []byte {\n\treturn append(nonce, ciphertext...)\n}\n\n// Splits nonce from ciphertext\nfunc splitNonceCiphertext(blob []byte, nonceSize int) ([]byte, []byte, error) {\n\tif len(blob) <= nonceSize {\n\t\treturn nil, nil, fmt.Errorf(\"invalid ciphertext blob - blob shorter than nonce length\")\n\t}\n","sourceCodeStart":119,"sourceCodeEnd":155,"githubUrl":"https://github.com/slackhq/nebula/blob/dd8f660c0ac37903ec4080ca4d3c861ba9342ceb/cert/crypto.go#L119-L155","documentation":"deriveKey rejects Argon2 parameters whose salt is shorter than 16 bytes (128 bits). Argon2 requires a sufficiently long salt to prevent precomputation/rainbow-table attacks, so this library enforces the NIST-recommended 128-bit minimum before calling argon2.IDKey. If aes256DeriveKey is given a passphrase-encryption setup with a missing/short salt, key derivation aborts.","triggerScenarios":"Calling aes256DeriveKey (via EncryptAndMarshalSigningPrivateKey or DecryptAndUnmarshalSigningPrivateKey) with Argon2Parameters whose Salt field is a non-nil byte slice of length 1-15. A nil salt produces the distinct 'salt must be set' error first; this error fires only when salt exists but len(salt) < 16.","commonSituations":"Hand-crafting NebulaEncryptedData or Argon2Parameters structs in tests/tools; truncating a salt from an older or external encryption tool; copying a hardcoded example salt that is too short; migrating certs produced by non-nebula tooling with 8-byte salts.","solutions":["Generate a 16-byte (or larger) cryptographically random salt, e.g. salt := make([]byte, 16); rand.Read(salt), and set params.Salt before calling the encrypt/derive API","Check where the salt was produced - if it came from serialized data, re-encrypt with a proper-length salt rather than padding the short one","Verify you are not accidentally passing a truncated slice (e.g. salt[:8]) when assembling argon2Parameters"],"exampleFix":"// before\nparams := &cert.Argon2Parameters{Salt: []byte(\"shortsalt\"), ...}\nkey, err := cert.DeriveKey(passphrase, params)\n// after\nsalt := make([]byte, 16)\nif _, err := rand.Read(salt); err != nil { return err }\nparams := &cert.Argon2Parameters{Salt: salt, ...}\nkey, err := cert.DeriveKey(passphrase, params)","handlingStrategy":"validation","validationCode":"if params.Salt == nil || len(params.Salt) < 16 { return fmt.Errorf(\"salt must be >= 16 bytes, got %d\", len(params.Salt)) }\n// then call aes256DeriveKey / EncryptAndMarshalSigningPrivateKey","typeGuard":"func hasValidSalt(p *cert.Argon2Parameters) bool { return p != nil && len(p.Salt) >= 16 }","tryCatchPattern":"key, err := cert.DeriveKey(pass, params)\nif err != nil {\n    if strings.Contains(err.Error(), \"salt must be\") { /* regenerate salt */ }\n    return err\n}","preventionTips":["Always generate salts with crypto/rand at >= 16 bytes","Never hardcode or reuse salts; generate a fresh one per encryption","Validate Argon2Parameters in a constructor/helper before use"],"tags":["crypto","argon2","salt","validation"],"backgroundTag":"weak-crypto-parameter","analyzedSha":"dd8f660c0ac37903ec4080ca4d3c861ba9342ceb","analyzedAt":"2026-09-03T11:13:55.444Z","contentChangedAt":"2026-09-03T11:13:55.444Z","schemaVersion":2},"datasetVersion":"2026-09-10T17:17:09.494Z"}