com.kolich.common.util.secure.KolichStringSigner.java Source code

Java tutorial

Introduction

Here is the source code for com.kolich.common.util.secure.KolichStringSigner.java

Source

/**
 * Copyright (c) 2015 Mark S. Kolich
 * http://mark.koli.ch
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

package com.kolich.common.util.secure;

import com.kolich.common.KolichCommonException;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.regex.Pattern;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.kolich.common.util.crypt.Base64Utils.*;
import static org.apache.commons.codec.binary.StringUtils.getBytesUtf8;
import static org.apache.commons.codec.binary.StringUtils.newStringUtf8;

public final class KolichStringSigner {

    private static final String SIGNATURE_DELIMITER = "|";
    private static final String ALGORITHM_SHA_256 = "SHA-256";

    /**
     * The secret key used during signing.
     */
    private final String secret_;

    /**
     * Digest used to generate a signature.
     */
    private final MessageDigest digest_;

    public KolichStringSigner(String algorithmName, String secret) throws NoSuchAlgorithmException {
        checkNotNull(secret, "Signature secret cannot be null.");
        secret_ = secret;
        digest_ = MessageDigest.getInstance((algorithmName != null) ?
        // If we were given an algorithm by name, attempt to use it.
        // Otherwise, just use the default.
                algorithmName : ALGORITHM_SHA_256);
    }

    public KolichStringSigner(String secret) throws NoSuchAlgorithmException {
        this(null, secret);
    }

    /**
     * Given a payload String to sign, concatenates it together with the
     * default delimiter followed by our secret.  The resulting String
     * is a base-64 URL-safe encoded signature that consists of
     * "payload|signature" where payload is likely to be an email address
     * but can be any String.
     */
    public final String sign(final String payload) {
        checkNotNull(payload, "Oops, the payload string to sign cannot " + "be null.");
        final String signed;
        synchronized (digest_) {
            signed = newStringUtf8(encodeBase64(digest_.digest(getBytesUtf8(
                    // Relies on string concatenation. The payload really
                    // shouldn't be huge but who knows.
                    payload + secret_))));
        }
        return encodeBase64URLSafe(
                // We have to base-64 encode the payload in the event that it
                // might contain a "|" which is our default signature delimiter.
                encodeBase64(payload) +
                // A "|" to separate the payload and signature.
                        SIGNATURE_DELIMITER +
                        // The resulting signature, the signed payload and secret.
                        signed);
    }

    /**
     * Given a signature to validate, returns the String payload contained in
     * the signature if the signature matches the signed payload.  The
     * signed payload is most often an email address, but can be any String.
     */
    public final String isValid(final String signature) {
        checkNotNull(signature, "Oops, the signature to validate " + "cannot be null.");
        String certified = null;
        try {
            // Extract the payload from the signature, may be null
            // if no payload was found (e.g., a bogus signature).
            final String payload = extractPayload(signature);
            // Resign the extracted payload and make sure it matches
            // the input signature.  If it matches, when we know the
            // signature string and the contents inside came from us!
            if (sign(payload).equals(signature)) {
                certified = payload;
            } else {
                throw new StringSignerException(
                        "Signed payload (" + payload + ") does not match signature (" + signature + ")");
            }
        } catch (Exception e) {
            throw new StringSignerException("Failed to validate input " + "signature: " + signature, e);
        }
        return certified;
    }

    /**
     * Given a signature string, extract the relevant payload.
     * Returns null if no payload could be extracted from within the
     * signature.
     */
    private static final String extractPayload(final String signature) {
        checkNotNull(signature, "Oops, the signature to extract a payload " + "from cannot be null.");
        // The signature is a base-64 encoded mess of "payload|signature"
        final String[] tokens = decodeBase64(signature).split(Pattern.quote(SIGNATURE_DELIMITER));
        return (tokens.length >= 1) ? decodeBase64(tokens[0]) : null;
    }

    /**
     * Inline exception class for the string signer.
     */
    public static final class StringSignerException extends KolichCommonException {

        private static final long serialVersionUID = -3848061272831604919L;

        public StringSignerException(String message, Throwable cause) {
            super(message, cause);
        }

        public StringSignerException(String message) {
            super(message);
        }

    }

}