I am trying to connect my Metamask wallet to my Java Spring-Boot backend. I was trying to follow the example here. I am able to autogenerate the nonce and receive the wallet ID without a problem. I am trying to verify the signed nonce from the Wallet on the server to make sure that the sender is indeed who they say they are. However, I am unable to find any documentation on Web3J to do this.
Is web3j not the right package to use for this? The example shows how to do the verification on NodeJS based on javascript but I don't find any example on how to do this on Java.
My understanding is that the public key is the wallet ID itself and that the message is the nonce signed by the private key of the wallet which is not shared for obvious reasons. According to this, I would need to "decrypt" the message using the public key and see if the decrypted message is same as the nonce that the backend sent to Metamask to sign. Is this correct?
Here is my code to create and send the nonce to UI:
public User findUserByPublicAddress(String publicWalletId) {
User u = userRepository.findByPublicWalletId(publicWalletId);
if(u == null) {
u = new User("", "", "", null, publicWalletId, "");
String nonce = StringUtil.generateRandomAlphaNumericString();
u.setNonce(nonce);
userRepository.saveAndFlush(u);
}
return u;
}
Here, I see if the user is already in my system and if they are not, then I just create a temporary user with a random nonce generated and saved in the DB. This nonce is sent to the UI for Metamask to sign. However, I am not sure how to do the verification part of it.
I was able to figure this out finally. My initial understanding was incorrect. I was not supposed to attempt to decrypt the message to retrieve the nonce. Rather I needed to use the nonce to see if I can retrieve the public key of the private key used to sign the message and see if that public key retrieved matches the wallet ID.
The algorithm:
Receive the signed message and the wallet ID from the client
Retrieve the nonce sent to the client with the same wallet ID
Generate the hash of the nonce
Generate the signature data from the message. This basically retrieves the V, R and S and. R and S are the outputs of the ECDSA Signature and V is the Recovery ID.
Using the ECDSA Signature and Hash of the Nonce, generate the possible public Key that was used to sign the message. At max, one will be able to generate 4 possible public keys for this message.
Check if any of the generated keys match public wallet ID that the client sent. If it matches, then we have a positive match. Generate the JWT and respond to the client. If not, we know that the nonce was not signed by the Metamask wallet we expected.
The Code:
Here is a sample code for UI (JavaScript and HTML):
web3.eth.sign(
web3.utils.sha3(nonce),
window.userWalletAddress)
.then((message) => {
console.log(message)
data['message'] = message // BODY
var xmlReq = new XMLHttpRequest();
xmlReq.onreadystatechange = function() {
if(this.readyState == 4 && this.status == 200) {
response = this.responseText
console.log(response)
}
};
xmlReq.open("POST", "/api/users/login", true)
xmlReq.setRequestHeader('Content-Type', 'application/json')
xmlReq.send(JSON.stringify(data))
})
The web3.eth.sign() takes the message to be signed and takes the wallet ID that is signing it. This is then sent to the backend. In the backend:
public User signin(UserLoginDTO loginDetails, HttpServletResponse response) {
try {
// Get the wallet ID and signed message from the body stored in the DTO
String publicWalletId = loginDetails.getPublicWalletId();
String message = loginDetails.getMessage();
// Find the nonce from the DB that was used to sign this message
User user = userRepository.findByPublicWalletId(publicWalletId);
String nonce = user.getNonce();
// Generate the HASH of the Nonce
byte[] nonceHash = Hash.sha3(nonce.getBytes()) // org.web3j.crypto.Hash
// Generate the Signature Data
byte[] signatureBytes = Numeric.hexStringToByteArray(message); // org.web3j.utils.Numeric
byte v = (byte) ((signatureBytes[64] < 27) ? (signatureBytes[64] + 27) : signatureBytes[64]);
byte[] r = Arrays.copyOfRange(signatureBytes, 0, 32);
byte[] s = Arrays.copyOfRange(signatureBytes, 32, 64);
SignatureData signatureData = new SignatureData(v, r, s); // org.web3j.crypto.Sign.SignatureData
// Generate the 4 possible Public Keys
List<String> recoveredKeys = new ArrayList<>();
for(int i = 0; i < 4; i++) {
BigInteger r = new BigInteger(1, signatureData.getR());
BigInteger s = new BigInteger(1, signatureData.getS());
ECDSASignature ecdsaSignature = new ECDSASignature(r, s);
BigInteger recoveredKey = Sign.recoverFromSignature((byte)i, ecdsaSignature, nonceHash);
if(recoveredKey != null) {
recoveredKeys.add("0x" + Keys.getAddressFromKey(recoveredKey)); // org.web3j.crypto.Keys
}
}
// Check if one of the generated Keys match the public wallet ID.
for(String recoveredKey : recoveredKeys) {
if(recoveredKey.equalsIgnoreCase(publicWalletId)) {
// Add Code here to create the JWT and add that to your HttpServletResponse. Not shown here.
return user;
}
}
throw new CustomException("Message Sign Invalid", HttpStatus.UNAUTHORIZED);
}
catch (Exception ex) {
// Custom Error Handling.
}
}
Related
I have compiled my .proto file using the protobuf compiler and received a selection of Java files. I received a proto.java file and a .java file for each item in the .proto file, including the message type and each RPC call e.g. publicKeyRequest.java and Quote.java as the RPC and request parameter type.
Is this all the files that are needed as I still cannot seem to to get any simple response back from the server?
I want to generate a request for the PublicKeyRequest RPC call. I generated the request object, but I do not know how to actually send it via the channel.
This is the full .proto file:
syntax = "proto3";
option java_multiple_files = true;
option java_package = "io.grpc.decryptiondevice";
option java_outer_classname = "DecryptionDeviceProto";
package decryptiondevice;
service DecryptionDevice {
// Decryption Request RPC
//
// Request contains ciphertext and proof
// Returns the plaintext record
rpc DecryptRecord(DecryptionRequest) returns (Record) {}
// Get Signed Root Tree Hash RPC
//
// Caller provides a nonce
// Returns a signed RTH and nonce
rpc GetRootTreeHash(RootTreeHashRequest) returns (RootTreeHash) {}
// Get Public key RPC
//
// Returns a Remote attestation report containing the public key as user data
rpc GetPublicKey(PublicKeyRequest) returns (Quote) {}
}
// Decryption Request
// - Byte array containing ciphertext
// - Proofs represented as JSON trees
message DecryptionRequest {
bytes ciphertext = 1;
string proofOfPresence = 2;
string proofOfExtension = 3;
}
// A plaintext record
message Record {
bytes plaintext = 1;
}
// RTH request contains
// - A random nonce
message RootTreeHashRequest {
bytes nonce = 1;
}
// Root Tree Hash
// Random nonce used as message ID
// Signature over rth and nonce
message RootTreeHash {
bytes rth = 1;
bytes nonce = 2;
bytes sig = 3;
}
// Public key request message
message PublicKeyRequest {
bytes nonce = 1;
}
// Attestation Quote, containing the public key
message Quote {
string quote = 1; //some format.. to be defined later
//PEM formatted key
bytes RSA_EncryptionKey = 2;
bytes RSA_VerificationKey = 3;
}
And this is the code I am attempting to run on the client side:
public static void main(String[] args) {
DeviceClient client = new DeviceClient("localhost", 50051);
MannagedChanel channel = ManagedChannelBuilder.forAddress("localhost", 50051).usePlaintext(true);
ByteString nonce = ByteString.copyFromUtf8("someRandomString");
PublicKeyRequest keyRequest = PublicKeyRequest.newBuilder().setNonce(nonce).build();
// Here I want to send this to the server
ByteString response = DecryptionDeviceProto.getKey(keyRequest, channel);//this line is not even close to being valid, but this is the sort thing I wish to achieve
Sys.out.println(response);
}
Apologies if this is very wrong, I am new to gRPC.
A few points about this system:
A client and server has already been written in Go which has been tested and works with this same .proto file.
I am attempting to rewrite the client in Java to communicate with the same server.
There are two sets of files that need to be generated: Java Protobuf and Java gRPC. To my knowledge, for all languages except Go, these are two separate generation steps (that can be combined into one protoc invocation, but they are conceptually separate).
It seems you are generating the Java Protobuf code, but not the Java gRPC code. You need to use the protoc-gen-grpc-java plugin to protoc. If you are using Maven or Gradle, read grpc-java's README. If you are running protoc manually, you can download a pre-built binary from Maven Central and see an answer to a similar question.
I want to encrypt the final grades of students in PHP and decrypt it in Android Java. I referred my codes here but it returns wrong value.
This is my PHP encryption function
function encode5t($value1){
for($i=0;$i<3;$i++)
{
$value1=base64_encode(strrev($value1));
}
return $value1;
}
Call the function:
foreach ($rows as $row){
$post["cSemester"] = $row["cSemester"];
$post["cSchoolYear"] = $row["cSchoolYear"];
$post["cSubjectCode"] = $row["cSubjectCode"];
$post["cDescription"] = $row["cDescription"];
$post["nFGrade"] = encode5t($row["nFGrade"]);
$post["nCGrade"] = $row["nCGrade"];
$post["nCredit"] = $row["nCredit"];
//update our response JSON data
array_push($response["posts"], $post);
}
echo json_encode($response);
And this is my Java code.
vGrades = json.getJSONArray(TAG_POSTS);
for (int i = 0; i < vGrades.length(); i++) {
JSONObject c = vGrades.getJSONObject(i);
String cSemester = c.getString(TAG_SEMESTER);
String cSchoolYear = c.getString(TAG_SCHOOLYEAR);
String cSubjectCode = c.getString(TAG_SUBJECTCODE);
String cDescription = c.getString(TAG_DESCRIPTION);
String encrypted_string = c.getString(TAG_FINALGRADE);
String nCGrade = c.getString(TAG_COMPLETIONGRADE);
String nCredit = c.getString(TAG_CREDIT);
HashMap<String, String> map = new HashMap<String, String>();
try{
byteArray = Base64.decode(encrypted_string, Base64.DEFAULT);
decrypt = new String(byteArray, "UTF-8");
}catch (UnsupportedEncodingException e) {
e.printStackTrace();
} // this is where I want to decrypt it.
nFGrade = decrypt;
map.put(TAG_SEMESTER, cSemester);
map.put(TAG_SCHOOLYEAR, cSchoolYear);
map.put(TAG_SUBJECTCODE, cSubjectCode);
map.put(TAG_DESCRIPTION, cDescription);
map.put(TAG_FINALGRADE, nFGrade);
map.put(TAG_COMPLETIONGRADE, nCGrade);
map.put(TAG_CREDIT, nCredit);
ViewGrades.add(map);
}
The PHP encryption is running .. but when i decrypt it the system returns another encrypted value .. for example the fGrade is 1.0.
the PHP encypted String value is: "PT1RVERSRGU="
the Java decrypted value is: "==QTDRDe"
where did I go wrong? I need help please ..thanks guys!
Base64 has no key, anyone can decode it, change it and replace it. But that may be all you need if you just want to thwart the casual user. You need to define who you are protecting against knowing that every scheme can be compromised.
To encrypt data use AES. Encryption is not easy to get correct. The PHP mcrypt encryption function is flawed, do not use it. Consider RNCryptor-php, it provides a full solution including authentication and key derivation.
I am using jersey rest webservice along with JWT with RSA signature token feature for authentication. I was able to successfully create and send token to the front-end. Now after I have achieved this far I am confused about verifying token and also identifying a user making request for resources.
Few questions here:
Do I have to decode the jwt token received on front-end to check the
claims?
How do I identify a user requesting for a resource on backend?
Because on few of the posts on SO some people said its not required to decode the token on front end (check this link) while other examples on other sites shows example of decoding the token on front end such as this
Now I am confused how to go further on whether I should actually decode a token on front-end or leave it as is? If so how come other examples show decoding on front-end like this:
angular.module('app')
.factory('Auth', ['$http', '$localStorage', 'urls', function ($http, $localStorage, urls) {
function urlBase64Decode(str) {
var output = str.replace('-', '+').replace('_', '/');
switch (output.length % 4) {
case 0:
break;
case 2:
output += '==';
break;
case 3:
output += '=';
break;
default:
throw 'Illegal base64url string!';
}
return window.atob(output);
}
function getClaimsFromToken() {
var token = $localStorage.token;
var user = {};
if (typeof token !== 'undefined') {
var encoded = token.split('.')[1];
user = JSON.parse(urlBase64Decode(encoded));
}
return user;
}
Token example I am using here:
private void authenticate(String email, String password)
throws Exception {
try {
Connection con = DBConnection.getConnection();
PreparedStatement statement = con.prepareStatement("select USR_PRIMARY_EMAIL, USR_PASSWORD from TBL_USER where USR_PRIMARY_EMAIL=? and USR_PASSWORD=?");
statement.setString(1, email);
statement.setString(2, password);
ResultSet result = statement.executeQuery();
if (result.next()) {
System.out.println("User authenticated successfully");
KeyPairGenerator keyGenerator = KeyPairGenerator.getInstance("RSA");
keyGenerator.initialize(1024);
KeyPair kp = keyGenerator.genKeyPair();
RSAPublicKey publicKey = (RSAPublicKey) kp.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) kp.getPrivate();
JWSSigner signer = new RSASSASigner(privateKey);
JWTClaimsSet claimsSet = new JWTClaimsSet();
claimsSet.setSubject("alice");
claimsSet.setIssuer("https://c2id.com");
claimsSet.setExpirationTime(new Date(new Date().getTime() + 60 * 1000));
System.out.println("publicKey is: " + publicKey);
System.out.println("privateKey is: " + privateKey);
System.out.println("claimsSet is: " + claimsSet);
SignedJWT signedJWT = new SignedJWT(new JWSHeader(JWSAlgorithm.RS256),claimsSet);
signedJWT.sign(signer);
token = signedJWT.serialize();
System.out.println("Token is: " + token);
signedJWT = SignedJWT.parse(token);
System.out.println("signedJWT is: " + signedJWT);
JWSVerifier verifier = new RSASSAVerifier(publicKey);
assertTrue(signedJWT.verify(verifier));
assertEquals("alice", signedJWT.getJWTClaimsSet().getSubject());
assertEquals("https://c2id.com", signedJWT.getJWTClaimsSet().getIssuer());
assertTrue(new Date().before(signedJWT.getJWTClaimsSet().getExpirationTime()));
} else {
System.out.println("User doesn't exist");
}
} catch (Exception e) {
System.out.println("DB related Error");
e.printStackTrace();
}
}
Also one more issue is the token generated using nimbus+jose_JWT(RSA signature) I am not able to decode in angular auth0 library. Is it because I am using public key?
Do I have to decode the jwt token received on front-end to check the claims?
Yes. The JWT claims set is base64URL-encoded JSON, so you need to decode in order to read it.
How do I identify a user requesting for a resource on backend?
The sub claim is optional, but practically every JWT provider will issue all tokens with a subject ID that identifies the requester. From the JWT spec:
The "sub" (subject) claim identifies the principal that is the
subject of the JWT. The claims in a JWT are normally statements
about the subject. The subject value MUST either be scoped to be
locally unique in the context of the issuer or be globally unique.
The processing of this claim is generally application specific. The
"sub" value is a case-sensitive string containing a StringOrURI
value. Use of this claim is OPTIONAL.
Also one more issue is the token generated using nimbus+jose_JWT(RSA signature) I am not able to decode in angular auth0 library. Is it because I am using public key?
No. All JWT claim sets are base64URL-encoded JSON independently of signature method, so you should be able to decode it.
I'm using Play framework to develop consumer for Instagram real-time API. But still could not perform x-hub-signature verification properly. So, how can we perform Instagram x-hub-signature verification using Java and Play framework?
Here is my current code:
From the Play framework, I obtain the JSON payload using this method:
public static Result receiveInstaData(){
JsonNode json = request().body().asJson();
//obtain the x-hub-signature from the header
//obtain the corresponding client secret
VerificationResult verificationResult =
SubscriptionUtil.verifySubscriptionPostSignature(
clientSecret, json.toString(), xHubSignature);
if(verificationResult.isSuccess()){
//do something
}
}
Then inside the SubscriptionUtil, I perform verification using this following code:
public static VerificationResult verifySubscriptionPostSignature(String clientSecret, String rawJsonData, String xHubSignature) {
SecretKeySpec keySpec;
keySpec = new SecretKeySpec(clientSecret.getBytes("UTF-8"), HMAC_SHA1);
Mac mac;
mac = Mac.getInstance(HMAC_SHA1);
mac.init(keySpec);
byte[] result;
result = mac.doFinal(rawJsonData.getBytes("UTF-8"));
String encodedResult = Hex.encodeHexString(result);
return new VerificationResult(encodedResult.equals(xHubSignature), encodedResult);
}
I created a standalone Python script that copies the instagram-python implementation and both of them produce the same results for the same clientSecret and jsonString. Maybe I should provide with raw binary data instead of String.
If let's say we need a raw binary data for JSON request, then I need to create my custom BodyParser to parse the JSON request to raw binary data[5]
References:
[1-4]http://pastebin.com/g4uuDwzn (SO doesn't allow me to post more than 2 links, so I put all the references here. The links contain the signature verification in Ruby, Python and PHP)
[5]https://groups.google.com/forum/#!msg/play-framework/YMQb6yeDH5o/jU8FD--yVPYJ
[6]My standalone python script:
#! /usr/bin/env python
import sys
import hmac
import hashlib
hc_client_secret = "myclientsecret"
hc_raw_response = "[{\"subscription_id\":\"1\",\"object\":\"user\",\"object_id\":\"1234\",\"changed_aspect\":\"media\",\"time\":1297286541},{\"subscription_id\":\"2\",\"object\":\"tag\",\"object_id\":\"nofilter\",\"changed_aspect\":\"media\",\"time\":1297286541}]"
client_secret = hc_client_secret
raw_response = hc_raw_response
if len(sys.argv) != 3:
print 'Usage verify_signature <client_secret> <raw_response>.\nSince the inputs are invalid, use the hardcoded value instead!'
else:
client_secret = sys.argv[1]
raw_response = sys.argv[2]
print "client_secret = " + client_secret
print "raw_response = " + raw_response
digest = hmac.new(client_secret.encode('utf-8'), msg=raw_response.encode('utf-8'), digestmod=hashlib.sha1).hexdigest()
print digest
Finally I managed to find the solution. For the Controller in Play Framework, we need to use BodyParser.Raw so the we can extract the payload request as raw data, i.e. array of bytes.
Here's the code for the controller in Play Framework:
#BodyParser.Of(BodyParser.Raw.class)
public static Result receiveRawInstaData(){
Map<String, String[]> headers = request().headers();
RawBuffer jsonRaw = request().body().asRaw();
if(jsonRaw == null){
logger.warn("jsonRaw is null. Something is wrong with the payload");
return badRequest("Expecting serializable raw data");
}
String[] xHubSignature = headers.get(InstaSubscriptionUtils.HTTP_HEADER_X_HUB_SIGNATURE);
if(xHubSignature == null){
logger.error("Invalid POST. It does not contain {} in its header", InstaSubscriptionUtils.HTTP_HEADER_X_HUB_SIGNATURE);
return badRequest("You are not Instagram!\n");
}
String json;
byte[] jsonRawBytes;
jsonRawBytes = jsonRaw.asBytes();
json = new String(jsonRawBytes, StandardCharsets.UTF_8);
try {
String clientSecret = InstaSubscriptionUtils.getClientSecret(1);
VerificationResult verificationResult = SubscriptionUtil.verifySubscriptionPostRequestSignature
(clientSecret,jsonRawBytes, xHubSignature[0]);
if(verificationResult.isSuccess()){
logger.debug("Signature matches!. Received signature: {}, calculated signature: {}", xHubSignature[0], verificationResult.getCalculatedSignature());
}else{
logger.error("Signature doesn't match. Received signature: {}, calculated signature: {}", xHubSignature[0], verificationResult.getCalculatedSignature());
return badRequest("Signature does not match!\n");
}
} catch (InstagramException e) {
logger.error("Instagram exception.", e);
return internalServerError("Internal server error. We will attend to this problem ASAP!");
}
logger.debug("Received xHubSignature: {}", xHubSignature[0]);
logger.info("Sucessfully received json data: {}", json);
return ok("OK!");
}
And for the code for method verifySubscriptionPostRequestSignature in SubscriptionUtil
public static VerificationResult verifySubscriptionPostRequestSignature(String clientSecret, byte[] rawJsonData, String xHubSignature) throws InstagramException{
SecretKeySpec keySpec;
keySpec = new SecretKeySpec(clientSecret.getBytes(StandardCharsets.UTF_8), HMAC_SHA1);
Mac mac;
try {
mac = Mac.getInstance(HMAC_SHA1);
mac.init(keySpec);
byte[] result = mac.doFinal(rawJsonData);
String encodedResult = Hex.encodeHexString(result);
return new VerificationResult(encodedResult.equals(xHubSignature), encodedResult);
} catch (NoSuchAlgorithmException e) {
throw new InstagramException("Invalid algorithm name!", e);
} catch (InvalidKeyException e){
throw new InstagramException("Invalid key: " + clientSecret, e);
}
}
I implemented this solution in jInstagram, here is the link to the source code: SubscriptionUtil
After a day of long programming I want to post something useful for someone else.
Few days ago I was wondering how to handle JPasswordField's method getPassword() with a correct procedure to pass a value to a server and get an answer.
and this is the question:
How can I correctly get a value from a JPasswordField in a safe way and handle it to create a login procedure with a server?
This is the solution I reached.
First of all I decided the procedure of the login that was safe enough to my purpose, I didn't want to send a plain password to the server and I didn't want to store a plain password (obviously) in my database.
The first thing to say is that a good way to secure a password is that it is never ever exchanged over a network in a plain and readable form, this is obviously because of a possible "man in the middle", that in few words is someone reading your messages in their way to the server.
The password need to be hashed, that means that it is transformed to a quite long sequence of hexadecimal characters. The good thing of the hash is that is (hopefully) one-way. You can't de-hash a password.
There are many algorithms to do this, I choose the SHA256.
The password is then hashed, but just between us, this can be not enough. If an hacker is able to steal the hash there are some techniques that can bring him to a successful "translation" of it. Just to add a variable in his equation, and to make his life harder, we can add a salt to the password prior to hash it. A salt is a piece of string that is added in any position we desire to the password. This avoids some kind of attacks based on dictionary and most used password.
But if an hacker that is better trained than me can't read the password, how can I?
The answer is simple, I don't have to.
But to understand this we need to jump for a moment in the "Registration procedure" that is the moment when a new user is added to my database. This is it:
The client ask the server to be registered sending the nickname.
The server answer with a token that is the salt for the password.
The client salt the password, hash it and send it back to the server.
The server now receive something that is unreadable so there's no security problem, and store it with the nickname and the salt. The salted hashed password is the "common secret".
So the login procedure will be like this:
The client ask the server to login
the server answer with the salt
the client salts the password then hashes it and sends it back to the server.
the server compare the shared secret with the received string. If they are equals the user is allowed to login.
This should be quite fine, but in this case if an hacker knows the shared secret can access the server without any problem because doing so we just changed the password, not to be readable, but still usable directly.
To avoid this behavior we just have to add a passage in our chain:
The client ask the server to login
the server answers with the salt and a random session-salt
the client salts the password, hashes it. At this point it salts again the hash and re-hash it. Then it send the hashed-salted-hash-of-salted-password back to the server
the server takes the shared secret, salts it with the random session salt and then hashes it. If the two strings are equals then the user is allowed to login.
Now that the procedure is clear we have an issue to solve. If I handle any kind of String this can persist in the memory for long time, so if I put my password in a String it can be readable in a plain form for long time. This is not so good for us, but we are not the first to think about it, java indeed created a way to avoid this password persisting. The solution is use an array of characters. This is because even if the array is persisting in the memory, its datas are spread with no order in the memory and is very difficult to re-create the original password.
Re inventing hot water? Yep, just use the getPassword() method in a JPasswordField.
But this is quite difficult for newbie. We get a char[] array, and it is strange for a not expert.
The first thing that reach our mind can be to transform that array in a plain string .......
But is just what I want to avoid. So I need to handle the array as-is.
We need then a method to salt and hash the password, the result can be this:
public static String digestSalted(String salt, char[] password) throws NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance("SHA-256");
ArrayList<Byte> list = new ArrayList<Byte>();
for (int i = 0; i < password.length; i++) {
//String ch = String.valueOf(password[i]);
//byte[] b = ch.getBytes();
//for (int j = 0; j < b.length; j++) {
// list.add(b[j]);
//}
list.add((byte)password[i]);
}
byte[] saltInBytes = salt.getBytes();
byte[] toBeHashed = new byte[(saltInBytes.length + list.size())];
for (int i = 0; i < saltInBytes.length; i++) {
toBeHashed[i] = saltInBytes[i];
}
for (int i = saltInBytes.length; i < list.size() + saltInBytes.length; i++) {
toBeHashed[i] = list.get(i - saltInBytes.length);
}
md.update(toBeHashed);
byte byteData[] = md.digest();
StringBuffer hexString = new StringBuffer();
for (int i = 0; i < byteData.length; i++) {
String hex = Integer.toHexString(0xff & byteData[i]);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString();
}
this method create an array of bytes passing through many little strings then append the salt. Once salted it hash the result with SHA256.
Now the return can be a string because is hashed and there is no problem of security.
This give a solution for the first part of the question.
The second part is just to implements our protocol between server and client.
I will only show the code in the client that is significant enough to understand the procedure.
I am using a blocking queue where the messages is put when read from the socket. this is the code:
public void login(String nickname, char[] password) {
if (cl == null) {
throw new RuntimeException();
}
long s = Sys.getTime();
cl.send("NICK " + nickname);
IncomingMessage reply = null;
try {
reply = this.mh.getMessage(); //The response to NICK msg
if (reply.getCommand().equalsIgnoreCase("LOGIN")) {
ArrayList<String> params = reply.getParams();
String accountSalt = params.get(0);
String randomSalt = params.get(1);
try {
String sharedSecret = SHAHash.digestSalted(accountSalt, password);
String saltedSharedSecret = SHAHash.digestSalted(randomSalt, sharedSecret);
if (saltedSharedSecret != null) {
cl.send("PASS " + saltedSharedSecret);
reply = this.mh.getMessage();
if (reply.getCommand().equalsIgnoreCase("WELCOME") && reply.getParams().get(0).equals(nickname)) {
// ************ LOG ************ //
LOG.config("Logged in.");
// ***************************** //
this.running = true;
this.loggedIn = true;
mh.startExecutor();
LOG.config("Time passed: " + (Sys.getTime() - s));
mh.startGame();
} else {
// ************ LOG ************ //
LOG.warning("A problem has occured while trying to login to the server.");
// ***************************** //
JOptionPane.showMessageDialog(null, "Error while logging to the server, shutting down.\n- ERROR 006 -");
System.exit(0);
}
}
} catch (NoSuchAlgorithmException e) {
// ************ LOG ************ //
LOG.warning("Error while SHA hashing the password, shutting down.");
// ***************************** //
JOptionPane.showMessageDialog(null, "Error while SHA hashing the password, shutting down.\n- ERROR 005 -");
System.exit(0);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
The code, now that we have clear how the protocol works, it's easy to understand the thing that should be considered is that this.mh.getMessage() is a blocking method, this means that the thread will wait until something is available in the queue before trying to get it.
This (almost) how I solved my problem. Let me know if there is any error in the answer or if you need some clarification.
I hope this will be useful to someone. Have a nice programming