|
| 1 | +--- |
| 2 | +name: secure-message-digest-generation |
| 3 | +description: Generate secure message digest (often called hash) generation code. Enforces secure generation of a message digest. Invoke when writing any message digest generation related code. |
| 4 | +allowed-tools: Read Grep Glob |
| 5 | +metadata: |
| 6 | + category: security |
| 7 | +--- |
| 8 | + |
| 9 | +# Secure Message Digest Code Generation Rules |
| 10 | + |
| 11 | +Apply **all** rules below when generating or reviewing any code related to message digest generation. |
| 12 | + |
| 13 | +## 1. Secure algorithm usage and hash collision prevention (CRITICAL) |
| 14 | + |
| 15 | +- ALWAYS ensure that the hashing algorithm used is the strongest that exists, from a cryptography perspective, at the moment at which the code is generated. In case of any doubt always use `SHA3-512` algorithm. |
| 16 | +- ALWAYS ensure that the hashing algorithm used is post-quantum resistant. |
| 17 | +- ALWAYS ensure that each value used to create the digest is explicitly separated by the character `|` (appended after every value, even when only one value is provided) prior to the final concatenated value being provided to the hashing function. |
| 18 | +- ALWAYS encode the input string to bytes using the `UTF-8` charset explicitly — never rely on the platform default charset, as it may vary across environments and produce different digests for the same input. |
| 19 | +- ALWAYS convert null or empty values to an explicit empty string `""` before including them in the digest input — never skip or silently drop them, as omitting a value changes the digest in the same way a different value would. |
| 20 | +- ALWAYS encode the raw digest bytes as a lowercase hexadecimal string — never return raw bytes or use Base64, as hex is the canonical, human-readable, and interoperable representation for message digests. |
| 21 | + |
| 22 | +```java |
| 23 | +// BAD: Weak algorithm, platform charset, no separator, raw bytes returned |
| 24 | +public byte[] computeDigestInsecure(Object[] values) { |
| 25 | + StringBuilder combined = new StringBuilder(); |
| 26 | + for (Object value : values) { |
| 27 | + combined.append(value != null ? value.toString() : ""); // no separator — hash collision risk |
| 28 | + } |
| 29 | + MessageDigest md = MessageDigest.getInstance("SHA1"); // weak, not post-quantum resistant |
| 30 | + byte[] hashBytes = md.digest(combined.toString().getBytes()); // platform default charset — non-deterministic |
| 31 | + return hashBytes; // raw bytes — not hex-encoded |
| 32 | +} |
| 33 | + |
| 34 | +// GOOD: All rules applied |
| 35 | +public String computeDigestSecure(Object[] values) { |
| 36 | + StringBuilder combined = new StringBuilder(); |
| 37 | + for (Object value : values) { |
| 38 | + // Rule 5: explicit empty string for null/empty — never skip |
| 39 | + combined.append((value == null || value.toString().isEmpty()) ? "" : value.toString()); |
| 40 | + // Rule 3: separator after every value, including single values |
| 41 | + combined.append("|"); |
| 42 | + } |
| 43 | + // Rule 1 & 2: SHA3-512 — strongest available, post-quantum resistant |
| 44 | + MessageDigest md = MessageDigest.getInstance("SHA3-512"); |
| 45 | + // Rule 4: explicit UTF-8 — never rely on platform default |
| 46 | + byte[] hashBytes = md.digest(combined.toString().getBytes(StandardCharsets.UTF_8)); |
| 47 | + // Rule 6: lowercase hex — canonical, human-readable, interoperable |
| 48 | + String hexDigest = HexFormat.of().formatHex(hashBytes); |
| 49 | + return hexDigest; |
| 50 | +} |
| 51 | +``` |
| 52 | + |
| 53 | +## 2. Output Checklist |
| 54 | + |
| 55 | +Before finalizing generated code, verify: |
| 56 | + |
| 57 | +- [ ] The hashing algorithm used is the strongest that exists, from a cryptography perspective, at the moment at which the code is generated. |
| 58 | +- [ ] The hashing algorithm used is post-quantum resistant. |
| 59 | +- [ ] A separator character `|` is appended after each value (including single values) used to create the data provided to the hashing function. |
| 60 | +- [ ] The input string is encoded to bytes using the `UTF-8` charset explicitly, not the platform default. |
| 61 | +- [ ] Null or empty values are explicitly converted to an empty string `""` and included in the digest input — never skipped or dropped. |
| 62 | +- [ ] The digest output is encoded as a lowercase hexadecimal string — not returned as raw bytes or Base64. |
| 63 | + |
| 64 | +## References |
| 65 | + |
| 66 | +- [Post-Quantum Cryptography from NIST](https://csrc.nist.gov/projects/post-quantum-cryptography). |
| 67 | +- [SHA-3 Standard: Permutation-Based Hash and Extendable-Output Functions from NIST](https://csrc.nist.gov/pubs/fips/202/final). |
| 68 | +- [Flickr's API Signature Forgery Vulnerability from IOACTIVE](https://www.ioactive.com/wp-content/uploads/2012/08/flickr_api_signature_forgery.pdf). |
| 69 | +- [Everything you need to know about hash length extension attacks from SKULLSECURITY](https://www.skullsecurity.org/2012/everything-you-need-to-know-about-hash-length-extension-attacks). |
| 70 | +- [Cryptographic_Storage_Cheat_Sheet from OWASP](https://cheatsheetseries.owasp.org/cheatsheets/Cryptographic_Storage_Cheat_Sheet.html). |
| 71 | +- [Hexadecimal from Wikipedia](https://en.wikipedia.org/wiki/Hexadecimal). |
| 72 | +- [UTF-8 from Wikipedia](https://en.wikipedia.org/wiki/UTF-8). |
0 commit comments