I am facing a issue that whenever I am signing a token also I parse it and it is not throwing any signature exception.
You can see the key are different still it giving me the proper response.
public class JwtUtil {
public String parseToken(String token) {
try {
Jws<Claims> jwt = Jwts.parser()
.setSigningKey("Test#12")
.parseClaimsJws(token);
System.out.println(jwt.getBody().getSubject());
return "Valid";
} catch (SignatureException jwtException) {
jwtException.printStackTrace();
return null;
}
}
public String generateToken() {
Claims claim = Jwts.claims();
claim.put("GivenName", "Johnny");
claim.put("Surname", "Rocket");
claim.put("Email", "jrocket#example.com");
return Jwts.builder().setHeaderParam("typ", "JWT").setClaims(claim)
.setIssuer("Online JWT Builder")
.setAudience("www.example.com").setSubject("jrocket#example.com")
.signWith(SignatureAlgorithm.HS256, "Test#123").compact();
}
public static void main(String[] args) {
JwtUtil jwtUtil = new JwtUtil();
String token = jwtUtil.generateToken();
System.out.println(token);
JwtUtil jwtUtil1 = new JwtUtil();
jwtUtil1.parseToken(token);
}
}
Really Test#12 and Test#123 are the same key
It is due to JwtBuilder.signWith(SignatureAlgorithm alg, String base64EncodedSecretKey). assumes that you are providing a key in base64 and your keys are not base64. When the method decodes from base64 to byte[] the java converter used by jjwt provides a representation of the string. Test#12 and Test#123 are encoded with the byte array
See https://stackoverflow.com/a/38269014/6371459
You can test yourself with
System.out.println(
javax.xml.bind.DatatypeConverter.printBase64Binary(
javax.xml.bind.DatatypeConverter.parseBase64Binary("Test#12")));
System.out.println(
javax.xml.bind.DatatypeConverter.printBase64Binary(
javax.xml.bind.DatatypeConverter.parseBase64Binary("Test#123")));
Try a (more) different key and the SignatureException will be thrown
Related
I'm trying to create an authenticated websocket to wss://ws-feed-public.sandbox.exchange.coinbase.com and need to create a "signature" for my requests using the following:
import org.apache.commons.codec.digest.HmacAlgorithms;
import org.apache.commons.codec.digest.HmacUtils;
public class Signature {
private final String secretKey;
public Signature(final String secretKey) {
this.secretKey = secretKey;
}
public String generate(String requestPath, String method, String body, String timestamp) {
String message = timestamp + method.toUpperCase() + requestPath + body;
return new HmacUtils(HmacAlgorithms.HMAC_SHA_256, secretKey).hmacHex(message);
}
}
For this particular signature, requestPath is always a blank string, and method is always GET
I continuously get the following return:
{
"type":"error",
"message":"Authentication Failed",
"reason":"{\"message\":\"invalid signature\"}"
}
I have also tried utilizing Signature.java from Gdax-java https://github.com/irufus/gdax-java/blob/master/security/src/main/java/com/coinbase/exchange/security/Signature.java#L34 but to no avail.
What am I doing incorrectly? Any help is appreciated.
Update: I also tried setting requestPath to /users/self/verify.
I have a JWT with the secret key :
private final static Algorithm ALGORITHM = Algorithm.HMAC256("secret");
public String createToken(#NonNull String username) {
Timestamp timestamp = Timestamp.from(Instant.from(ZonedDateTime.now(ZoneId.of("Z"))));
Timestamp expTime = Timestamp.from(Instant.from(ZonedDateTime.now(ZoneId.of("Z")).plusMinutes(10)));
try {
String token = JWT.create()
.withIssuer("auth0")
.withClaim("username", username)
.withClaim("time", timestamp).withExpiresAt(expTime)
.sign(ALGORITHM);
return token;
and the verify method:
public DecodedJWT verifyToken(String token) {
DecodedJWT decodedJWT = JWT.decode(token);
try {
JWTVerifier verifier = JWT
.require(ALGORITHM)
.withIssuer("auth0")
.build();
DecodedJWT jwt = verifier.verify(token);
return jwt;
} catch (JWTVerificationException exception) {
System.out.println("token not verified");
}
My problem is that the token returned from the method has an invaild signature as by https://jwt.io/ . Also the verify method is not working because of that.
I read some blogs in which they said that you have to encode your secret, so I tried it like this:
private final static Algorithm ALGORITHM = Algorithm.HMAC256(Base64.getEncoder().encodeToString("secret".getBytes()));
but it didnt work the signature was also invalid. Has anyone an idea on how can I fix that?
Thanks in advance
Upgrading from:
import org.springframework.security.authentication.encoding.PasswordEncoder;
#Override
public String encodePassword(String plainPassword, Object salt) {
final String finalSalt = salt != null ? salt.toString() : "";
return DigestUtils.md5Hex(finalSalt + plainPassword);
}
#Override
public boolean isPasswordValid(String encodedPassword, String plainPassword, Object salt) {
final String enteredPassword = encodePassword(plainPassword, salt);
return encodedPassword.equals(enteredPassword);
}
To:
import org.springframework.security.crypto.password.PasswordEncoder;
#Override
public String encode(CharSequence rawPassword) {
final String finalSalt = salt != null ? salt.toString() : "";
return DigestUtils.md5Hex(finalSalt + plainPassword);
}
#Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
final String enteredPassword = encodePassword(plainPassword, salt);
return encodedPassword.equals(enteredPassword);
}
Not sure what to do about salt?
Not sure if I can just convert rawPassword to String to replace plainPassword?
The new methods expect that salt is part of the encoded password. As per PasswordEncoder.encoder() javadoc:
Encode the raw password. Generally, a good encoding algorithm applies a SHA-1 or greater hash combined with an 8-byte or greater randomly generated salt.
If you look at this answer it shows how BCryptPasswordEncoder encodes salt in the encoded password. The actual BCrypt encoded password format is explained here.
Here's my Java code, which is generating tokens:
private static final GzipCompressionCodec COMPRESSION_CODEC = new GzipCompressionCodec();
private String issuer;
private int expirationSec;
private int clockSkewSec;
private String secretKey;
//constructor, some other methods
public String createToken(final Map<String, String> attributes) {
Date now = new Date();
Date exp = new Date(System.currentTimeMillis() + (1000 * expirationSec));
Claims claims = Jwts.claims();
claims.putAll(attributes);
return Jwts
.builder()
.setClaims(claims)
.setIssuer(issuer)
.signWith(HS256, secretKey)
.compressWith(COMPRESSION_CODEC)
.setIssuedAt(now)
.setExpiration(exp)
.compact();
}
I can decode this token in Java without any problems, but when I try to decode it with https://jwt.io I get "Invalid Signature" error. I need to extract token expiration date. I've also tried to generate token without compression, but also fails.
Attempting to validate firebase id tokens using jjwt. Using GoogleCredential class to pull the private key. But I'm not sure if that's correct. Receiving an error: JWT signature does not match locally computed signature.Am I supposed to be using the private key here from service account json? Maybe I'm misunderstanding what ...setSigningKey(...) takes in.
#Service
public class FirebaseAuthVerifier implements AuthVerifier {
private static final Logger logger = LoggerFactory.getLogger(FirebaseAuthVerifier.class);
#Autowired
private FirebaseProperties fbProps;
public boolean verify(AuthToken token) throws GeneralSecurityException, IOException {
// get google credential
InputStream stream = new FileInputStream("src/main/resources/service-account.json");
ByteArrayOutputStream streamCopy = new ByteArrayOutputStream();
ByteStreams.copy(stream, streamCopy);
stream.close();
GoogleCredential gc = GoogleCredential.fromStream(
new ByteArrayInputStream(streamCopy.toByteArray()),
new NetHttpTransport(),
GsonFactory.getDefaultInstance());
try {
Jwts.parser().setSigningKey(gc.getServiceAccountPrivateKey()).parse(token.getTokenId());
} catch(Exception e) {
// log
logger.info("Firebase auth token verification error: ");
logger.info(e.getMessage());
// claims may have been tampered with
return false;
}
return true;
}
}
You're on the right lines! The key from the service account is used when creating JWTs to send to Google/Firebase. You really don't want to put that in your APK, as any malicious individual could steal it and use it to create ID tokens as you!
When you're validating a token from Firebase, you need to check Firebase's own keys - luckily, these are public! You can grab them from https://www.googleapis.com/robot/v1/metadata/x509/securetoken#system.gserviceaccount.com - they rotate every few hours. If you look in that file you'll see it's a JSON dictionary, like this:
"8226146523a1b8894ba03ad525667b9475d393f5": "---CERT---",
The key in this is the kid field in the header of the ID token JWT - it corresponds to the key the token was signed with, meaning the cert that corresponds can be used to verify the signature.
Take a look at the (server side) docs for validating ID tokens for more.
Using custom jwt id token validation
#Service
public class FirebaseAuthVerifier implements AuthVerifier {
private static final Logger logger = LoggerFactory.getLogger(FirebaseAuthVerifier.class);
private static final String pubKeyUrl = "https://www.googleapis.com/robot/v1/metadata/x509/securetoken#system.gserviceaccount.com";
/**
*
* #param token
* #return
* #throws GeneralSecurityException
* #throws IOException
*/
public boolean verify(AuthToken token) throws GeneralSecurityException, IOException {
// get public keys
JsonObject publicKeys = getPublicKeysJson();
// verify count
int size = publicKeys.entrySet().size();
int count = 0;
// get json object as map
// loop map of keys finding one that verifies
for (Map.Entry<String, JsonElement> entry: publicKeys.entrySet()) {
// log
logger.info("attempting jwt id token validation with: ");
try {
// trying next key
count++;
// get public key
PublicKey publicKey = getPublicKey(entry);
// validate claim set
Jwts.parser().setSigningKey(publicKey).parse(token.getTokenId());
// success, we can return
return true;
} catch(Exception e) {
// log
logger.info("Firebase id token verification error: ");
logger.info(e.getMessage());
// claims may have been tampered with
// if this is the last key, return false
if (count == size) {
return false;
}
}
}
// no jwt exceptions
return true;
}
/**
*
* #param entry
* #return
* #throws GeneralSecurityException
*/
private PublicKey getPublicKey(Map.Entry<String, JsonElement> entry) throws GeneralSecurityException, IOException {
String publicKeyPem = entry.getValue().getAsString()
.replaceAll("-----BEGIN (.*)-----", "")
.replaceAll("-----END (.*)----", "")
.replaceAll("\r\n", "")
.replaceAll("\n", "")
.trim();
logger.info(publicKeyPem);
// generate x509 cert
InputStream inputStream = new ByteArrayInputStream(entry.getValue().getAsString().getBytes("UTF-8"));
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate)cf.generateCertificate(inputStream);
return cert.getPublicKey();
}
/**
*
* #return
* #throws IOException
*/
private JsonObject getPublicKeysJson() throws IOException {
// get public keys
URI uri = URI.create(pubKeyUrl);
GenericUrl url = new GenericUrl(uri);
HttpTransport http = new NetHttpTransport();
HttpResponse response = http.createRequestFactory().buildGetRequest(url).execute();
// store json from request
String json = response.parseAsString();
// disconnect
response.disconnect();
// parse json to object
JsonObject jsonObject = new JsonParser().parse(json).getAsJsonObject();
return jsonObject;
}
}