{"record":{"id":"2b4ed3cf7dd0517c","repo":"trailofbits/algo","slug":"private-key-must-decode-to-exactly-32-bytes-got","errorCode":null,"errorMessage":"Private key must decode to exactly 32 bytes, got {len(priv_raw)}","messagePattern":"Private key must decode to exactly 32 bytes, got (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"library/x25519_pubkey.py","lineNumber":91,"sourceCode":"                        msg=f\"Private key file must be either base64 or exactly 32 raw bytes, got {len(data)} bytes\"\n                    )\n                priv_b64 = base64.b64encode(data).decode()\n        except OSError as e:\n            module.fail_json(msg=f\"Failed to read private key file: {e}\")\n    else:\n        priv_b64 = module.params[\"private_key_b64\"]\n\n    # Validate input parameters\n    if not priv_b64:\n        module.fail_json(msg=\"No private key provided\")\n\n    try:\n        priv_raw = base64.b64decode(priv_b64, validate=True)\n    except Exception as e:\n        module.fail_json(msg=f\"Invalid base64 private key format: {e}\")\n\n    if len(priv_raw) != 32:\n        module.fail_json(msg=f\"Private key must decode to exactly 32 bytes, got {len(priv_raw)}\")\n\n    try:\n        priv_key = x25519.X25519PrivateKey.from_private_bytes(priv_raw)\n        pub_key = priv_key.public_key()\n        pub_raw = pub_key.public_bytes(encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw)\n        pub_b64 = base64.b64encode(pub_raw).decode()\n        result[\"public_key\"] = pub_b64\n\n        if module.params[\"public_key_path\"]:\n            pub_path = module.params[\"public_key_path\"]\n            existing = None\n\n            try:\n                with open(pub_path) as f:\n                    existing = f.read().strip()\n            except OSError:\n                existing = None\n","sourceCodeStart":73,"sourceCodeEnd":109,"githubUrl":"https://github.com/trailofbits/algo/blob/20e22a8715c198b38c01c1ca62d0953b93587a74/library/x25519_pubkey.py#L73-L109","documentation":"The base64 decoded successfully but did not produce exactly 32 bytes — X25519 private keys must be 32 bytes, so any other length (commonly 33 or 31 from a corrupted key) is rejected before key construction.","triggerScenarios":"A valid-base64 string encoding 33/31/64 bytes: e.g. a key with one extra character encoded, an Ed25519 or hex key mislabeled as X25519, or double-encoded data.","commonSituations":"Mixing up key types between tools, encoding a 64-char hex string as base64, or hand-editing a key.","solutions":["Verify decode length in Python: len(base64.b64decode(key)) must equal 32","Regenerate the key with wg genkey or cryptography's X25519PrivateKey.generate()","Check you didn't concatenate public+private key material","Ensure upstream generation writes exactly 32 raw bytes"],"exampleFix":"# python check\nimport base64\nraw = base64.b64decode(open('priv.b64').read().strip(), validate=True)\nassert len(raw) == 32, len(raw)","handlingStrategy":"validation","validationCode":"import base64\nraw = base64.b64decode(priv_b64, validate=True)\nassert len(raw) == 32, f'X25519 key must be 32 bytes, got {len(raw)}'","typeGuard":"def is_32byte_key(b64: str) -> bool:\n    import base64\n    try:\n        return len(base64.b64decode(b64, validate=True)) == 32\n    except Exception:\n        return False","tryCatchPattern":null,"preventionTips":["Regenerate suspicious keys rather than hand-editing","Keep key type consistent (X25519) across the whole pipeline"],"tags":["x25519","key-length","wireguard","ansible"],"backgroundTag":"invalid-key-length","analyzedSha":"20e22a8715c198b38c01c1ca62d0953b93587a74","analyzedAt":"2026-08-28T13:26:02.752Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}