I have an encrypted value in database and I would like to decrypt it before sending to front-end.
When I first save the value as encrypted, it looks like -kKwj477382jle34nw in database. But if I call my getClientByUsername() function which I make the decryption thing in this function, the value in database also changes itself automatically when I set decrypted value in the object before sending the object to front-end.
#Transactional
public ResponseEntity <Client> getClientByUsername(String username) throws Exception {
Client loggedClient = clientDAO.findByUsername(username);
String data = loggedClient.getCreditCardNo();
if (null != data) {
#SuppressWarnings("static-access")
byte[] encrypted = base64.decodeBase64(data);
SecretKeySpec secretKeySpec = new SecretKeySpec(encryptionKey.getBytes(), algorithm);
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
decrypted = cipher.doFinal(encrypted);
loggedClient.setCreditCardNo(new String(decrypted));
}
return new ResponseEntity < Client > (loggedClient, HttpStatus.OK);
}
Here is how I save the value as encrypted:
#Transactional
public boolean clientUpdate(String client) {
str = updateclient.getCreditCardNo();
if (null != str) {
SecretKeySpec secretKeySpec = new SecretKeySpec(encryptionKey.getBytes("UTF-8"), algorithm);
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
encrypted = cipher.doFinal(str.getBytes("UTF-8"));
updateclient.setCreditCardNo(base64.encodeToString(encrypted));
return clientDAO.updateProfileClient(updateclient);
}
How can I block hibernate to change the value when calling setter?
Update
#PersistenceContext
private EntityManager entityManager;
public Client findByUsername(String username) throws Exception {
Query query = entityManager.createNamedQuery("Client.findByUsername");
query.setParameter("username", username);
List result = query.getResultList();
return result.size() > 0 ? (Client) result.get(0) : null;
}
You need to evict this object from Hibernate's session:
void evict(Object object) throws HibernateException
Remove this instance from the session cache. Changes to the instance
will not be synchronized with the database. This operation cascades to
associated instances if the association is mapped with
cascade="evict".
P.S. Just thought, that you can also consider another approach. You can create a field in bean which will be marked as #Transient, i.e. decoupled from the database, and name it, say, creditCardNoDecrypted. The encrypted field mark as #JsonIngore (or whatever you use for serialisation).
Related
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');
I’m trying to implement a solution with the encoder/decoder from the org.springframework.security.oauth2.jwt package with a shared secret.
But my attempt fails when I try to encode a token with a JwtEncodingException.
I have asked this question in another form, but here I include a simple ready to execute example, to verify the problem.
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.jwk.source.ImmutableSecret;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import org.springframework.security.oauth2.jwt.*;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
public class EncoderDecoderTest {
public static String secret = "j8IoV1jF67";
public JwtEncoder jwtEncoder() throws JOSEException {
SecretKey originalKey = new SecretKeySpec(secret.getBytes(), "AES");
JWKSource<SecurityContext> immutableSecret = new ImmutableSecret<SecurityContext>(originalKey);
return new NimbusJwtEncoder(immutableSecret);
}
public JwtDecoder jwtDecoder() {
SecretKey originalKey = new SecretKeySpec(secret.getBytes(), "AES");
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withSecretKey(originalKey).build();
return jwtDecoder;
}
public void tester() throws JOSEException {
JwtClaimsSet claims = JwtClaimsSet.builder()
.issuer("self") //Only this for simplicity
.build();
var encoder = jwtEncoder();
//This line throws the exception
String token = encoder.encode(JwtEncoderParameters.from(claims)).getTokenValue();
System.out.println(token);
var decoder = jwtDecoder();
Jwt jwt = decoder.decode(token);
System.out.println(jwt.getIssuer());
}
public static void main(String[] args) throws JOSEException {
new EncoderDecoderTest().tester();
}
}
jps has come up with a an explanation to why this doesn't work (AES is not a valid signature algorithm), thanks :-)
I guess my question could then be rephrased into: how do I implement a shared secret version of these two encoders/decoders, for example with HMAC using SHA-256?
org.springframework.security.oauth2.jwt.NimbusJwtDecoder
org.springframework.security.oauth2.jwt.NimbusJwtEncoder
I tried (a lot) but have not yet succeeded ;-(
Any suggestions for how to fix this problem?
With the input from jps (AES is actually OK for encrypted tokens, but not signed) and Github Copilot I came up with a working solution using HMAC-SHA256:
//Don't hardcode like this for real
public static String secret = "s/4KMb61LOrMYYAn4rfaQYSgr+le5SMrsMzKw8G6bXc=";
public JwtEncoder jwtEncoder() throws JOSEException {
SecretKey key = new SecretKeySpec(secret.getBytes(), "HmacSHA256");
JWKSource<SecurityContext> immutableSecret = new ImmutableSecret<SecurityContext>(key);
return new NimbusJwtEncoder(immutableSecret);
}
public JwtDecoder jwtDecoder() {
SecretKey originalKey = new SecretKeySpec(secret.getBytes(), "HmacSHA256");
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withSecretKey(originalKey).build();
return jwtDecoder;
}
public void tester() throws JOSEException {
//Create the TOKEN
JwtClaimsSet claims = JwtClaimsSet.builder()
.issuer("https://somewhere.com") //Only this for simplicity
.build();
var encoder = jwtEncoder();
JwsHeader jwsHeader = JwsHeader.with(() -> "HS256").build();
String token = encoder.encode(JwtEncoderParameters.from(jwsHeader, claims)).getTokenValue();
//Decode the TOKEN
var decoder = jwtDecoder();
Jwt jwt = decoder.decode(token);
System.out.println(jwt.getIssuer());
}
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;
}
}
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
I am using amazon s3 android low level sdk to upload a file and want to set md5 checksum to upload data.
1)Following is the code to create credentials:
BasicAWSCredentials lAwsCredentials = new BasicAWSCredentials(
Constants.ACCESS_KEY_ID, Constants.SECRET_KEY);
AmazonS3Client lS3Client = new AmazonS3Client(lAwsCredentials);
2)Following is the code to calculate md5
public class MD5CheckSum {
public static byte[] createChecksum(String pFilepath) throws Exception {
InputStream lFis = new FileInputStream(pFilepath);
byte[] lBuffer = new byte[1024];
MessageDigest lMessageDigest = MessageDigest.getInstance("MD5");
int lNumRead;
do {
lNumRead = lFis.read(lBuffer);
if (lNumRead > 0) {
lMessageDigest.update(lBuffer, 0, lNumRead);
}
} while (lNumRead != -1);
lFis.close();
return lMessageDigest.digest();
}
public static String getMD5Checksum(String pFilepath) throws Exception {
byte[] lBytes = createChecksum(pFilepath);
return Base64.encodeToString(lBytes, Base64.DEFAULT);
}
}
3)Following is the code to set md5 using metadata:
try {
lMd5 = MD5CheckSum.getMD5Checksum(pFile.getAbsolutePath());
Log.v(TAG, "CheckSum:====" + lMd5);
} catch (Exception lException) {
lException.printStackTrace();
}
ObjectMetadata lObjectMetadata = new ObjectMetadata();
if (lMd5 != null) {
lObjectMetadata.setContentMD5(lMd5);
}`
InitiateMultipartUploadResult mInitResponse = mS3Client.initiateMultipartUpload(new InitiateMultipartUploadRequest(mBucketName, mKeyName,
lObjectMetadata);
But a exception is thrown by amazon when i set md5:
Caused by: com.amazonaws.services.s3.model.AmazonS3Exception: Anonymous users cannot initiate multipart uploads. Please authenticate. (Service: Amazon S3; Status Code: 403; Error Code: AccessDenied; Request ID: BA0C68FC884703FD), S3 Extended Request ID: re2sdbzf8MMqGAyrNQOoqYJ8EdXERoWE7cjG+UpfAtFuP5IeAbXmk6Riw+PX8Uw3Jcspn1rSQvI=
Is this the correct way to set md5?
Note: When md5 is not set(ie objectmetadata is not set) then upload works without any exception
I have also faced this type of issue..
I fixed by adding Base64.DEFAULT instead of others such as WRAP and NO_WRAP.