Sending a Signed Message

Overview

Suppose Alice wants to send a message to Bob, but the message is in danger of being altered on its way. Bob wants to make sure the message received is authentic, i.e. it is not altered.

How can this be achieved?

Prerequisites

keytool -genkeypair                                 \
        -alias alice-key-pair                       \
        -keyalg RSA                                 \
        -keysize 2048                               \
        -sigalg SHA256withRSA                       \
        -keystore alice.p12                         \
        -storetype pkcs12                           \
        -storepass i-am-alice                       \
        -keypass i-am-alice                         \
        -dname "CN=CN OU=OU, O=O, L=L, ST=ON, C=CA" \
        -noprompt                                   \
        -validity 36500        
keytool -export               \
        -keystore alice.p12   \
        -alias alice-key-pair \
        -file alice.cert      \
        -storepass i-am-alice
keytool -importcert         \
        -file alice.cert    \
        -keystore bob.p12   \
        -alias "alice-cert" \
        -storepass i-am-bob \
        -noprompt

Java Application

Directory Layout

.
├── App.java
├── alice.cert
├── alice.p12
└── bob.p12

App.java

class SignedMessage {
    String msg;
    byte[] sign;
}

class Alice {
    SignedMessage signedMessage() throws Exception {
        try (InputStream is = new FileInputStream("alice.p12")) {
            KeyStore ks = KeyStore.getInstance("pkcs12");
            char[] pw = "i-am-alice".toCharArray();
            ks.load(is, pw);
            Key privateKey = ks.getKey("alice-key-pair", pw);

            Signature signature = Signature.getInstance("SHA256withRSA");
            signature.initSign((PrivateKey) privateKey);

            String msg = "My Secret Message";
            signature.update(msg.getBytes(UTF_8));
            byte[] sign = signature.sign();
            
            SignedMessage signedMessage = new SignedMessage();
            signedMessage.msg = msg;
            signedMessage.sign = sign;
            return signedMessage;
        }
    }
}

/**
 * Bob has access to his keystore, where Alice 's certificate is loaded.
 * Bob does not have access to Alice 's private key or any of her passwords.
 */
class Bob {
    boolean receive(SignedMessage signedMessage) throws Exception {
        InputStream is = new FileInputStream("bob.p12");        
        KeyStore keyStore = KeyStore.getInstance("pkcs12");
        keyStore.load(is, "i-am-bob".toCharArray());

        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initVerify(keyStore.getCertificate("alice-cert"));
        signature.update(signedMessage.msg.getBytes(UTF_8));

        is.close();
        return signature.verify(signedMessage.sign);
    }
}

class App {
    public static void main(String[] args) throws Exception {
        SignedMessage signedMessage = new Alice().signedMessage();
        boolean isAuthentic = new Bob().receive(signedMessage);
        System.out.println(isAuthentic);
    }
}

Sample Execution

Running the following commands will output true in the console since the message is not altered.

javac -d target App.java
mv *.p12 ./target/
mv *.cert ./target/
cd target
java App

To see an example of case where the message can not be verified, modify the main method slightly as seen below.

SignedMessage signedMessage = new Alice().signedMessage();
signedMessage.msg = signedMessage.msg + "-modified";

Executing the modified version will print false since the message is altered and it can not be authenticated.