Skip to content

Instantly share code, notes, and snippets.

@jamsesso
Last active December 28, 2015 16:48
Show Gist options
  • Save jamsesso/0eadef568a0e5e3f1137 to your computer and use it in GitHub Desktop.
Save jamsesso/0eadef568a0e5e3f1137 to your computer and use it in GitHub Desktop.
import com.google.gson.Gson;
import java.util.Date;
import lombok.Data;
import com.google.common.collect.Maps;
import java.util.Map;
/**
* The message class.
* This class should be present on both the client as well as the receiving app.
* You can serialize this class to send it over the network. JSON is a good choice.
*/
@Data
public class Message {
private final long timestamp;
private final Object publicKey;
private final String type;
private final String payload;
private final String signature;
public Message(String type, Object publicKey, Object secretKey, Object payload) {
SignatureGenerator signatureGenerator = new SignatureGenerator();
Gson gson = new Gson();
this.timestamp = new Date().getTime() / 1000;
this.publicKey = publicKey;
this.type = type;
this.payload = gson.toJson(payload);
Map<String, String> parameters = Maps.newHashMap();
parameters.put("payload", this.payload);
parameters.put("publicKey", publicKey.toString());
parameters.put("timestamp", Long.toString(this.timestamp));
parameters.put("type", type);
String digest = Util.createQueryString(parameters);
this.signature = signatureGenerator.generate(secretKey.toString(), digest);
}
}
import com.google.common.collect.Maps;
import java.util.Map;
import java.util.Optional;
/**
* The Message Authenticator is used on the receiving app side.
* When a message is received, pass it to this class to check that the generated signatures are equal.
*/
public class MessageAuthenticator {
private final SignatureGenerator signatureGenerator;
public MessageAuthenticator(SignatureGenerator signatureGenerator) {
this.signatureGenerator = signatureGenerator;
}
public boolean authenticate(Message message) {
long publicKey = message.getPublicKey();
Optional<String> maybeSecretKey = getSecretKeySomehow(publicKey);
if(!maybeSecretKey.isPresent()) {
// Couldn't find a secret key with that public key.
return false;
}
Map<String, String> parameters = Maps.newHashMap();
parameters.put("payload", message.getPayload());
parameters.put("publicKey", Long.toString(message.getPublicKey()));
parameters.put("timestamp", Long.toString(message.getTimestamp()));
parameters.put("type", message.getType());
String digest = Util.createQueryString(parameters);
String secretKey = maybeSecretKey.get();
String signature = signatureGenerator.generate(secretKey, digest);
return signature.equalsIgnoreCase(message.getSignature());
}
}
import java.nio.charset.Charset;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import lombok.extern.slf4j.Slf4j;
/**
* A signature generator class.
* This class should also be present on both the client and receiving app.
*/
@Slf4j
public class SignatureGenerator {
public static final char[] HEX_ARRAY = "0123456789abcdef".toCharArray();
public String generate(String key, String message) {
String signature = "";
Charset encoding = Charset.forName("UTF-8");
try {
Mac mac = Mac.getInstance("HMACSHA256");
Key symmetricKey = new SecretKeySpec(key.getBytes(encoding), "HMACSHA256");
mac.init(symmetricKey);
signature = bytesToHex(mac.doFinal(message.getBytes(encoding)));
}
catch(NoSuchAlgorithmException e) {
log.error("No HMAC SHA256 algorithm available for generating MAC", e);
}
catch(InvalidKeyException e) {
log.error("Invalid key passed to Sha256 signature service: {}", key, e);
}
return signature;
}
private static String bytesToHex(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
for(int j = 0; j < bytes.length; j++) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = HEX_ARRAY[v >>> 4];
hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
}
return new String(hexChars);
}
}
import java.util.Map;
import java.io.UnsupportedEncodingException;
import static java.net.URLEncoder.encode;
import static java.util.Map.Entry.comparingByKey;
/**
* The Util class can be used on the client and server to help format signatures.
*/
public class Util {
public static final String URL_ENCODING = "UTF-8";
public static String createQueryString(Map<String, String> parameters) {
return parameters.entrySet().stream()
.filter(p -> p.getKey() != null && p.getValue() != null)
.sorted(comparingByKey())
.map(p -> urlEncode(p.getKey()) + "=" + urlEncode(p.getValue()))
.reduce((left, right) -> left + "&" + right)
.orElse("");
}
public static String urlEncode(String raw) {
try {
return encode(raw, URL_ENCODING);
}
catch(UnsupportedEncodingException e) {
throw new UnsupportedOperationException(e);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment