{"record":{"id":"eefc747d9c666f8f","repo":"dromara/Sa-Token","slug":"invalid-hex-character-at-position-i-or-i-1","errorCode":null,"errorMessage":"Invalid hex character at position ${i} or ${i+1}","messagePattern":"Invalid hex character at position (.+?) or (.+?)","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"sa-token-core/src/main/java/cn/dev33/satoken/util/SaHexUtil.java","lineNumber":64,"sourceCode":"     * 将十六进制字符串转换为字节数组（JDK8兼容）\n     * @param hexString 有效的十六进制字符串（不区分大小写）\n     * @return 对应的字节数组\n     * @throws IllegalArgumentException 输入字符串格式错误时抛出异常\n     */\n    public static byte[] hexToBytes(String hexString) {\n        if (hexString == null) return null;\n        int len = hexString.length();\n        if (len % 2 != 0) {\n            throw new IllegalArgumentException(\"Hex string must have even length\");\n        }\n\n        byte[] data = new byte[len / 2];\n        for (int i = 0; i < len; i += 2) {\n            int high = Character.digit(hexString.charAt(i), 16);\n            int low = Character.digit(hexString.charAt(i+1), 16);\n\n            if (high == -1 || low == -1) {\n                throw new IllegalArgumentException(\n                        \"Invalid hex character at position \" + i + \" or \" + (i+1)\n                );\n            }\n\n            data[i/2] = (byte) ((high << 4) + low);\n        }\n        return data;\n    }\n\n}","sourceCodeStart":46,"sourceCodeEnd":74,"githubUrl":"https://github.com/dromara/Sa-Token/blob/ac2c7f6e94a78573cf0bcb932dd8b04e68fad189/sa-token-core/src/main/java/cn/dev33/satoken/util/SaHexUtil.java#L46-L74","documentation":"The second validation inside SaHexUtil.hexToBytes: after the even-length check passes, each character pair is converted with Character.digit(c, 16); a return of -1 means the char is not a hex digit, and the method throws IllegalArgumentException naming the offending position(s). Any non-[0-9a-fA-F] character — whitespace, '0x' prefix, 'g'-'z', punctuation — triggers it.","triggerScenarios":"hexToBytes(\"0x1A2B\") (contains 'x'), hexToBytes(\"1a 2b\") (space), hexToBytes(\"hello\") — any string with even length but non-hex characters.","commonSituations":"Digests copied with a '0x' prefix or embedded whitespace/newline (terminal line-wrap); hex read from a file that includes a trailing '\\n'; case/charset confusion after passing through a non-hex-safe transformation.","solutions":["Strip non-hex characters before decoding: hex = hex.replaceAll(\"[^0-9a-fA-F]\", \"\")","Remove '0x' prefixes and trim whitespace/newlines at the source","Validate first with a regex check: hex.matches(\"([0-9a-fA-F]{2})*\")"],"exampleFix":"// before\nSaHexUtil.hexToBytes(raw); // raw = \"0x1a2b\" -> throws\n\n// after\nString hex = raw.startsWith(\"0x\") ? raw.substring(2) : raw;\nSaHexUtil.hexToBytes(hex.trim());","handlingStrategy":"type-guard","validationCode":"String cleaned = hex.trim();\nif (cleaned.startsWith(\"0x\") || cleaned.startsWith(\"0X\")) {\n    cleaned = cleaned.substring(2);\n}\ncleaned = cleaned.replaceAll(\"[^0-9a-fA-F]\", \"\");","typeGuard":"public static boolean isValidHexString(String s) {\n    return s != null && s.length() % 2 == 0 && s.matches(\"(?:[0-9a-fA-F]{2})+\");\n}","tryCatchPattern":"try {\n    byte[] b = SaHexUtil.hexToBytes(hex);\n} catch (IllegalArgumentException e) {\n    // e.getMessage() names the offending position; fix the producer of the string\n}","preventionTips":["Strip '0x' prefixes, whitespace and newlines before decoding","Validate with a hex regex before calling hexToBytes","When copying digests from terminals, beware hidden line-wrap characters"],"tags":["sa-token","hex","encoding","input-validation"],"backgroundTag":null,"analyzedSha":"ac2c7f6e94a78573cf0bcb932dd8b04e68fad189","analyzedAt":"2026-08-14T14:36:10.271Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}