Previous | Next | Trail Map | Putting It All Together | BINGO!

Signing and Verifying the BINGO Cards

When a Player joins a game by calling the mayIPlay method, the Game generates the cards for the Player to play with. Before sending the cards back to the Player, the Game digitally signs the cards using the APIs in the java.security package.

Later, when a Player claims to have a winning card, the Game verifies the signature to make sure the card was created by this Game for the current game.

Let's look at the code in the Game that does this. All of the code related to signing and verifying cards is in NotaryPublic, an object created and used by the RingMaster.


Note: This section discusses how BINGO uses the security APIs in the JDK to sign cards and verify signatures. It does not talk about the general use of the security APIs. For that, see our online security trail Java Security 1.1(in the Java Security 1.1 trail).

Setting Up to Sign and Verify Cards

Following is the constructor for the NotaryPublic class:
NotaryPublic() {
    KeyPair pair = null;
    try {
        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("DSA");
        keyGen.initialize(1024, new SecureRandom());
        pair = keyGen.generateKeyPair();
    } catch (Exception e) {
        ErrorMessages.error("Cannot sign cards. Continuing anyway.");
    }
    priv = pair.getPrivate();
    pub = pair.getPublic();
}
This constructor generates a public and private key pair and then assigns each member of the pair to separate member variables. The private key of a key pair is used to generate signatures. The public key of a key pair is used to verify them. The NotaryPublic uses the same key pair, the one created in the constructor, to generate and verify the signatures for all of the cards created for this Game.

Signing the Cards

NotaryPublic contains a method signTheCard that generates a signature for the Card passed into it:
private void signTheCard(Card c, int gameNumber) throws NoSuchAlgorithmException,
							InvalidKeyException,
							SignatureException
{
    Signature dsa = Signature.getInstance("SHA/DSA");
    byte[] values = new byte[Card.SIZE*Card.SIZE+1];

    dsa.initSign(priv);

    for (int i = 0; i < Card.SIZE; i++)
        for (int j = 0; j < Card.SIZE; j ++)
	    values[Card.SIZE*i + j] = (byte)c.boardValues[i][j].number;
    values[values.length-1] = (byte)gameNumber;

    dsa.update(values);
    c.setSignature(dsa.sign());
}
This method creates a Signature object and, using the private key from the KeyPair, initializes the Signature object for signing. Next, the method fills a byte array with the values from the card and the current game number. The method uses this byte array to update the Signature object. Then, the method gets the signature from the Signature object and sets the signature on the Card to it.

Note that each value on the Card and the current game number are converted to bytes. Each value on a Card is in the range 1 to 75 and easily fits into a byte. However, if the game number exceeds 255 (the Game does not protect against this), then the conversion from an int to a byte will truncate the [PENDING: check this] high byte of the integer representing the game number. This could allow a sneaky Player to use a card signed for one game in another game.

The Signature class supports a version of the update method that lets you update the signature a byte at a time. However, this is not efficient (too many method calls), so the signTheCard method creates and fills a byte array and updates the signature with single method call instead. Generally speaking this is a better way of updating signatures.

The signTheCard method gets called whenever a Player registers for a game. The Player remotely calls the mayIPlay method in RegistrarImpl. If registration is allowed, mayIPlay generates cards for the player and calls signTheCard once for each Card to sign it. The Cards are passed back to the Player along with their signatures.

When a player detects a BINGO and clicks the "I Won" button, the Player notifies the Game of the win by remotely calling the BINGO method and passing in the winning card. The Card contains its signature which must be verified by the NotaryPublic's verifyTheSignature method to win.

Verifying the Signature

When a Player has a winning card, it calls the remote method BINGO in RegistrarImpl. This method calls RingMaster.verify which subsequently calls the verifyTheSignature method in the NotaryPublic to make sure that the Card was created and signed by this Game for the current game:
boolean verifyTheSignature(Card c, int gameNumber) {
    try {
        Signature dsa = Signature.getInstance("SHA/DSA");
        byte[] values = new byte[Card.SIZE*Card.SIZE+1];

        dsa.initVerify(pub);

        for (int i = 0; i < Card.SIZE; i ++)
	    for (int j = 0; j < Card.SIZE; j ++)
	        values[Card.SIZE*i + j] = (byte)c.boardValues[i][j].number;
        values[values.length-1] = (byte)gameNumber;

        dsa.update(values);
        return dsa.verify(c.getSignature());
    } catch (Exception e) {
        return false;
    }
}
The verifyTheSignature method is very similar to signTheCard but worth a few notes. This method also creates a Signature but instead of using the private key from the KeyPair, it uses the public key, and initializes the Signature for verification with the public key.

As with signTheCard, this method creates and fills a byte array with the values from the Card and the current game number, then updates the Signature object with the byte array. Finally, the method verifies the signature on the card by passing the Card's signature to the Signature object's verify method.

If the values on the Card are different than when the card was signed then the signature won't verify. Also, if the game number is different than when the card was signed the signature won't verify. This protects against Players trying to cheat by generating cards based on the announced balls, or using a card that was signed for a different game.


Previous | Next | Trail Map | Putting It All Together | BINGO!