Upgrading Spring Security PasswordEncoder - java

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.

Related

Checkmarx issue : Heap Inspection

Checkmarx complains "Method changePassword defines oldPassword, which is designated to contain user passwords. However, while plaintext passwords are later assigned to oldPassword, this variable is never cleared from memory. It this a false positive?
#PutMapping(path = "/changepassword", produces = APPLICATION_JSON_VALUE)
public ResponseEntity<String> changePassword(#RequestBody UserUpdate user, HttpServletRequest request, HttpServletResponse response) {
String uid= user.getId();
String oldPassword = user.getOldPwrd();
String newPassword = user.getPwrd();
userDetails.changeUserPassword(uid, oldPassword, newPassword);
return ResponseEntity.ok(SUCCESS);
}
It is considered as a best security practice to not store passwords in immutable strings and use an encrypted memory object such as SealedObject. This specialized class can store encrypted data in memory and helps ensure that it can't be easily retrieved from memory.
#PutMapping(path = "/changepassword", produces = APPLICATION_JSON_VALUE)
public ResponseEntity<String> changePassword(#RequestBody UserUpdate user, HttpServletRequest request, HttpServletResponse response) {
String uid= user.getId();
SealedObject oldPassword = user.getOldPwrd();
SealedObject newPassword = user.getPwrd();
userDetails.changeUserPassword(uid, oldPassword, newPassword);
return ResponseEntity.ok(SUCCESS);
}
You will have to change your changeUserPassword method to handle SealedObject which involves defining a cipher and key for encryption:
Key key = getKeyFromConfiguration();
Cipher c = Cipher.getInstance(CIPHER_NAME);
c.init(Cipher.ENCRYPT_MODE, key);
List<Character> characterList = Arrays.asList(input);
password = new SealedObject((Serializable) characterList, c);
Arrays.fill(input, '\0');

Coinbase websocket feed Invalid signature Java

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.

Invalid signature when trying to verify JWT

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

How to encode password with HMAC-SHA512 in Spring Boot Security

I have an old application running in PHP that uses the function base64_encode(hash_hmac(“sha512”, $p_password, $p_salt, true)) to encode passwords in our database.
I am migrating this application to Java Spring Boot and want to encode the passwords during authentification in the exact same way.
I have found how to make the same hashing method with Java in this post Compute HMAC-SHA512 with secret key in java and I also learnt that we can have several password encoders for old and new users with https://spring.io/blog/2017/11/01/spring-security-5-0-0-rc1-released#constructing-delegatingpasswordencoder
But I still cannot find an example of how I can integrate this hasing method in Spring authentication process. I have to create a PasswordEncoder bean and I don't know what to put inside.
I tried Pbkdf2PasswordEncoder because it can make some SHA-512 hash like in my app but I get the error Detected a Non-hex character at 1 or 2 position.
It is probably due to the fact that the passwords are not prefixed by {pbkdf2} in the database. The following code is what I am currently using as PasswordEncoder
#Bean
public PasswordEncoder passwordEncoder() {
Pbkdf2PasswordEncoder passwordEncoder = new Pbkdf2PasswordEncoder("salt");
passwordEncoder.setAlgorithm(Pbkdf2PasswordEncoder.SecretKeyFactoryAlgorithm.PBKDF2WithHmacSHA512);
return passwordEncoder;
}
I need help to set up the right password encoder to use HMAC-SHA512 in my authentification process with Java Spring and in a second time, combine it with BCrytPasswordEncoder (for new users) with DelegatingPasswordEncoder.
Maybe it requires to update the passwords in DB to prefix them with the right encoder ?
If my question is not accurate enough or missing information, please ask me for more details :)
You need to add a DelegatingPasswordEncoder to your project configuration file.
The DelegatingPasswordEncoder acts as a PasswordEncoder, and we use it when we have to choose from a collection of implementations.:
#Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {
#Bean
public PasswordEncoder passwordEncoder() {
Map<String, PasswordEncoder> encoders = new HashMap<>();
Pbkdf2PasswordEncoder bcryprPe = new Pbkdf2PasswordEncoder("salt");
bcryprPe.setAlgorithm(
Pbkdf2PasswordEncoder.SecretKeyFactoryAlgorithm.PBKDF2WithHmacSHA512);
encoders.put("pbkdf2", pbkdf2Pe);
// add other PasswordEncoder here:
encoders.put("scrypt", new SCryptPasswordEncoder());
return new DelegatingPasswordEncoder("pbkdf2", encoders);
}
}
With
return new DelegatingPasswordEncoder("pbkdf2", encoders);
we are saying to Spring Security: "Use the 'pbkdf2' as default password encoder".
If the provided hash is {scrypt}12345, the DelegatingPasswordEncoder delegates to the SCryptPasswordEncoder, if there is no prefix, the application will use default one.
I finally got what I wanted. I created an implementation of PasswordEncoder inspired by https://github.com/lathspell/java_test/blob/master/java_test_openldap/src/main/java/LdapSha512PasswordEncoder.java
in WebSecurityConfig.java
#Bean
public PasswordEncoder passwordEncoder() {
Map<String, PasswordEncoder> encoders = new HashMap<>();
encoders.put("SSHA-512", new Hmac512PasswordEncoder("salt"));
encoders.put("bcrypt", new BCryptPasswordEncoder());
return new DelegatingPasswordEncoder("SSHA-512", encoders);
}
in Hmac512PasswordEncoder.java
public class Hmac512PasswordEncoder implements PasswordEncoder {
private static final String SSHA512_PREFIX = "{SSHA-512}";
private static final String HMAC_SHA512 = "HmacSHA512";
private final String salt;
public Hmac512PasswordEncoder(String salt) {
if (salt == null) {
throw new IllegalArgumentException("salt cannot be null");
}
this.salt = salt;
}
public String encode(CharSequence rawPassword) {
String result = null;
try {
Mac sha512Hmac = Mac.getInstance(HMAC_SHA512);
final byte[] byteKey = Utf8.encode(salt);
SecretKeySpec keySpec = new SecretKeySpec(byteKey, HMAC_SHA512);
sha512Hmac.init(keySpec);
byte[] macData = sha512Hmac.doFinal(Utf8.encode(rawPassword.toString()));
result = SSHA512_PREFIX + Base64.getEncoder().encodeToString(macData);
//result = bytesToHex(macData);
} catch (InvalidKeyException | NoSuchAlgorithmException e) {
e.printStackTrace();
}
return result;
}
public boolean matches(CharSequence rawPassword, String encodedPassword) {
if (rawPassword == null || encodedPassword == null) {
return false;
}
String encodedRawPass = encode(rawPassword);
return MessageDigest.isEqual(Utf8.encode(encodedRawPass), Utf8.encode(encodedPassword));
}
}

JWT Token verification with Java

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

Categories

Resources