注册用户遇ECDSA签名密钥错误,JwtService类问题排查
问题描述
注册新用户时碰到两个错误:
先是出现:
ECDSA signing keys must be PrivateKey instances.] with root cause
修改签名算法后又出现:
secret token was not secure enough
问题出在以下JwtService类中:
@Service public class JwtService { //constant secret key that must change private static final String PRIVATE_KEY_PATH = "C:\\Users\\ihebt\\Documents\\security\\security\\src\\main\\resources/private.key"; public String extractUserEmail(String jwtToken) { return extractClaim(jwtToken, Claims::getSubject); } public String generateToken(UserDetails userDetails) { return generateToken(new HashMap<>(), userDetails); } public String generateToken( Map<String, Object> extraClaims, UserDetails userDetails ) { return Jwts .builder() .setClaims(extraClaims) .setSubject(userDetails.getUsername()) .setIssuedAt(new Date(System.currentTimeMillis())) .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 24)) .signWith(getSignInKey(), SignatureAlgorithm.ES256) .compact(); } public <T> T extractClaim(String token, Function<Claims, T> claimsResolver){ final Claims claims = extractAllClaims(token); return claimsResolver.apply((claims)); } public boolean isTokenValid(String token, UserDetails userDetails) { final String useremail = extractUserEmail(token); return (useremail.equals(userDetails.getUsername())) && !isTokenExpired(token); } private boolean isTokenExpired(String token) { return extractExpiration(token).before(new Date()); } private Date extractExpiration(String token) { return extractClaim(token, Claims::getExpiration); } private Claims extractAllClaims(String jwt_token) { return Jwts .parserBuilder() .setSigningKey(getSignInKey()) .build() .parseClaimsJws(jwt_token) .getBody(); } private Key getSignInKey() { try { byte[] privateKeyBytes = Files.readAllBytes(Paths.get(PRIVATE_KEY_PATH)); return Keys.hmacShaKeyFor(privateKeyBytes); } catch (IOException e) { throw new RuntimeException("Failed to read private key file", e); } } }
问题根源
- 第一个错误:代码使用
ES256(ECDSA算法)签名,但getSignInKey()方法生成的是HMAC-SHA密钥,ECDSA算法要求必须使用EC私钥,密钥类型不匹配导致报错。 - 第二个错误:切换到HMAC类算法后,密钥文件内容长度不足,HS256这类HMAC算法要求密钥至少32字节(256位),长度不够就会触发“secret token不够安全”的提示。
解决办法
办法1:继续使用ECDSA(ES256)算法
修改getSignInKey()方法,正确加载PEM格式的EC私钥:
private Key getSignInKey() { try { byte[] privateKeyBytes = Files.readAllBytes(Paths.get(PRIVATE_KEY_PATH)); // 解析PEM格式的EC私钥 PEMParser pemParser = new PEMParser(new StringReader(new String(privateKeyBytes))); JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC"); PrivateKey privateKey = converter.getPrivateKey((PEMKeyPair) pemParser.readObject()); pemParser.close(); return privateKey; } catch (IOException e) { throw new RuntimeException("Failed to read private key file", e); } }
需要添加BouncyCastle依赖(Spring Boot项目可将以下内容加到pom.xml):
<dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk15on</artifactId> <version>1.70</version> </dependency>
同时确保private.key是PEM格式的EC私钥,示例格式如下:
-----BEGIN EC PRIVATE KEY----- MHcCAQEEIF+...(省略具体内容)...o/5ZAgMBAAE= -----END EC PRIVATE KEY-----
办法2:改用HMAC(HS256)算法
- 修改
generateToken()方法中的签名算法:
.signWith(getSignInKey(), SignatureAlgorithm.HS256)
- 修改
getSignInKey()方法,确保密钥长度达标:
private Key getSignInKey() { try { byte[] keyBytes = Files.readAllBytes(Paths.get(PRIVATE_KEY_PATH)); // HS256要求密钥至少32字节 if (keyBytes.length < 32) { throw new IllegalArgumentException("HMAC key must be at least 32 bytes long for HS256"); } return Keys.hmacShaKeyFor(keyBytes); } catch (IOException e) { throw new RuntimeException("Failed to read secret key file", e); } }
可以用以下命令生成符合要求的密钥文件:
openssl rand -hex 32 > private.key
内容的提问来源于stack exchange,提问作者Timoumi
相关产品推荐
相关产品推荐

