All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.aholbrook.paseto.PasetoV1 Maven / Gradle / Ivy

package net.aholbrook.paseto;

import net.aholbrook.paseto.base64.Base64Provider;
import net.aholbrook.paseto.crypto.KeyPair;
import net.aholbrook.paseto.crypto.NonceGenerator;
import net.aholbrook.paseto.crypto.v1.V1CryptoProvider;
import net.aholbrook.paseto.encoding.EncodingProvider;
import net.aholbrook.paseto.exception.PasetoParseException;
import net.aholbrook.paseto.exception.SignatureVerificationException;
import net.aholbrook.paseto.util.ByteArrayUtils;
import net.aholbrook.paseto.util.PaeUtil;
import net.aholbrook.paseto.util.StringUtils;

public class PasetoV1 extends Paseto {
	private final static String VERSION = "v1";
	public final static String HEADER_LOCAL = VERSION + SEPARATOR + PURPOSE_LOCAL + SEPARATOR; // v1.local.
	public final static String HEADER_PUBLIC = VERSION + SEPARATOR + PURPOSE_PUBLIC + SEPARATOR; // v1.public.

	private final static byte[] HKDF_INFO_EK = StringUtils.getBytesUtf8("paseto-encryption-key");
	private final static byte[] HKDF_INFO_AK = StringUtils.getBytesUtf8("paseto-auth-key-for-aead");

	private final V1CryptoProvider cryptoProvider;

	private PasetoV1(Base64Provider base64Provider, EncodingProvider encodingProvider, V1CryptoProvider cryptoProvider,
			NonceGenerator nonceGenerator) {
		super(base64Provider, encodingProvider, nonceGenerator);
		this.cryptoProvider = cryptoProvider;
	}

	@Override
	public String encrypt(Object payload, byte[] key, String footer) {
		footer = StringUtils.ntes(footer); // convert null to ""
		byte[] payloadBytes = StringUtils.getBytesUtf8(encodingProvider.encode(payload));
		byte[] footerBytes = StringUtils.getBytesUtf8(footer);

		// Generate n
		byte[] random = nonceGenerator.generateNonce();
		byte[] n = new byte[V1CryptoProvider.NONCE_SIZE];
		System.arraycopy(cryptoProvider.hmacSha384(payloadBytes, random), 0, n, 0, n.length);

		// Split N into salt/nonce
		byte[] salt = new byte[V1CryptoProvider.HKDF_SALT_LEN];
		byte[] nonce = new byte[V1CryptoProvider.HKDF_SALT_LEN];
		System.arraycopy(n, 0, salt, 0, salt.length);
		System.arraycopy(n, salt.length, nonce, 0, nonce.length);

		// Create ek/ak for AEAD
		byte[] ek = cryptoProvider.hkdfExtractAndExpand(salt, key, HKDF_INFO_EK);
		byte[] ak = cryptoProvider.hkdfExtractAndExpand(salt, key, HKDF_INFO_AK);

		byte[] c = cryptoProvider.aes256CtrEncrypt(payloadBytes, ek, nonce);
		byte[] preAuth = PaeUtil.pae(StringUtils.getBytesUtf8(HEADER_LOCAL), n, c, footerBytes);
		byte[] t = cryptoProvider.hmacSha384(preAuth, ak);

		byte[] nct = new byte[n.length + c.length + t.length];
		System.arraycopy(n, 0, nct, 0, n.length);
		System.arraycopy(c, 0, nct, n.length, c.length);
		System.arraycopy(t, 0, nct, n.length + c.length, t.length);

		if (footerBytes.length > 0) {
			return HEADER_LOCAL + base64Provider.encodeToString(nct) + SEPARATOR
					+ base64Provider.encodeToString(footerBytes);
		} else {
			return HEADER_LOCAL + base64Provider.encodeToString(nct);
		}
	}

	@Override
	public <_Payload> _Payload decrypt(String token, byte[] key, String footer, Class<_Payload> payloadClass) {
		// Split token into sections
		String[] sections = split(token);
		if (sections == null) {
			throw new PasetoParseException(PasetoParseException.Reason.MISSING_SECTIONS, token);
		}

		// Check header
		checkHeader(token, sections, HEADER_LOCAL);

		// Decode footer
		String decodedFooter = decodeFooter(token, sections, footer);

		// Decrypt
		byte[] nct = base64Provider.decodeFromString(sections[2]);
		byte[] n = new byte[V1CryptoProvider.NONCE_SIZE];
		byte[] t = new byte[V1CryptoProvider.SHA384_OUT_LEN];
		// verify length
		if (nct.length < n.length + t.length + 1) {
			throw new PasetoParseException(PasetoParseException.Reason.PAYLOAD_LENGTH, token)
					.setMinLength(n.length + t.length + 1);
		}
		byte[] c = new byte[nct.length - n.length - t.length];
		System.arraycopy(nct, 0, n, 0, n.length);
		System.arraycopy(nct, n.length, c, 0, c.length);
		System.arraycopy(nct, n.length + c.length, t, 0, t.length);

		// Split N into salt/nonce
		byte[] salt = new byte[V1CryptoProvider.HKDF_SALT_LEN];
		byte[] nonce = new byte[V1CryptoProvider.HKDF_SALT_LEN];
		System.arraycopy(n, 0, salt, 0, salt.length);
		System.arraycopy(n, salt.length, nonce, 0, nonce.length);

		// Create ek/ak for AEAD
		byte[] ek = cryptoProvider.hkdfExtractAndExpand(salt, key, HKDF_INFO_EK);
		byte[] ak = cryptoProvider.hkdfExtractAndExpand(salt, key, HKDF_INFO_AK);

		byte[] preAuth = PaeUtil.pae(StringUtils.getBytesUtf8(HEADER_LOCAL), n, c,
				StringUtils.getBytesUtf8(decodedFooter));
		byte[] t2 = cryptoProvider.hmacSha384(preAuth, ak);
		if (!ByteArrayUtils.isEqual(t, t2)) {
			throw new SignatureVerificationException(token);
		}

		byte[] m = cryptoProvider.aes256CtrDecrypt(c, ek, nonce);

		// Convert from JSON
		return decode(m, payloadClass);
	}

	@Override
	public String sign(Object payload, byte[] pk, String footer) {
		footer = StringUtils.ntes(footer); // convert null to ""
		byte[] payloadBytes = StringUtils.getBytesUtf8(encodingProvider.encode(payload));
		byte[] footerBytes = StringUtils.getBytesUtf8(footer);

		byte[] m2 = PaeUtil.pae(StringUtils.getBytesUtf8(HEADER_PUBLIC), payloadBytes, footerBytes);
		byte[] sig = cryptoProvider.rsaSign(m2, pk);

		byte[] msig = new byte[sig.length + payloadBytes.length];
		System.arraycopy(payloadBytes, 0, msig, 0, payloadBytes.length);
		System.arraycopy(sig, 0, msig, payloadBytes.length, sig.length);

		if (footerBytes.length > 0) {
			return HEADER_PUBLIC + base64Provider.encodeToString(msig)
					+ SEPARATOR + base64Provider.encodeToString(footerBytes);
		} else {
			return HEADER_PUBLIC + base64Provider.encodeToString(msig);
		}
	}

	@Override
	public <_Payload> _Payload verify(String token, byte[] pk, String footer, Class<_Payload> payloadClass) {
		// Split token into sections
		String[] sections = split(token);
		if (sections == null) {
			throw new PasetoParseException(PasetoParseException.Reason.MISSING_SECTIONS, token);
		}

		// Check header
		checkHeader(token, sections, HEADER_PUBLIC);

		// Decode footer
		String decodedFooter = decodeFooter(token, sections, footer);

		// Verify
		byte[] msig = base64Provider.decodeFromString(sections[2]);
		byte[] s = new byte[V1CryptoProvider.RSA_SIGNATURE_LEN];
		// verify length
		if (msig.length < s.length + 1) {
			throw new PasetoParseException(PasetoParseException.Reason.PAYLOAD_LENGTH, token)
					.setMinLength(s.length + 1);
		}
		byte[] m = new byte[msig.length - s.length];
		System.arraycopy(msig, msig.length - s.length, s, 0, s.length);
		System.arraycopy(msig, 0, m, 0, m.length);

		byte[] m2 = PaeUtil.pae(StringUtils.getBytesUtf8(HEADER_PUBLIC), m, StringUtils.getBytesUtf8(decodedFooter));
		if (!cryptoProvider.rsaVerify(m2, s, pk)) {
			throw new SignatureVerificationException(token);
		}

		// Convert from JSON
		return decode(m, payloadClass);
	}

	@Override
	public KeyPair generateKeyPair() {
		return cryptoProvider.rsaGenerate();
	}

	public static class Builder {
		private final Base64Provider base64Provider;
		private final EncodingProvider encodingProvider;
		private final V1CryptoProvider v1CryptoProvider;
		private NonceGenerator nonceGenerator;

		public Builder(Base64Provider base64Provider, EncodingProvider encodingProvider, V1CryptoProvider v1CryptoProvider) {
			if (base64Provider == null) { throw new NullPointerException("Base64 provider is required."); }
			if (encodingProvider == null) { throw new NullPointerException("Encoding provider is required."); }
			if (v1CryptoProvider == null) { throw new NullPointerException("V1 Crypto Provider is required."); }

			this.base64Provider = base64Provider;
			this.encodingProvider = encodingProvider;
			this.v1CryptoProvider = v1CryptoProvider;
		}

		public Builder withTestingNonceGenerator(NonceGenerator nonceGenerator) {
			this.nonceGenerator = nonceGenerator;
			return this;
		}

		public PasetoV1 build() {
			if (nonceGenerator == null) { nonceGenerator = v1CryptoProvider.getNonceGenerator(); }
			return new PasetoV1(base64Provider, encodingProvider, v1CryptoProvider, nonceGenerator);
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy