001/* 002 * nimbus-jose-jwt 003 * 004 * Copyright 2012-2021, Connect2id Ltd and contributors. 005 * 006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use 007 * this file except in compliance with the License. You may obtain a copy of the 008 * License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software distributed 013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the 015 * specific language governing permissions and limitations under the License. 016 */ 017 018package com.nimbusds.jose.crypto.impl; 019 020import com.google.crypto.tink.subtle.XChaCha20Poly1305; 021import com.nimbusds.jose.JOSEException; 022import com.nimbusds.jose.util.ByteUtils; 023import com.nimbusds.jose.util.Container; 024import net.jcip.annotations.ThreadSafe; 025 026import javax.crypto.*; 027import java.security.*; 028 029/** 030 * This class defines the XChaCha20 stream cipher as well as the use of the Poly1305 authenticator. 031 * 032 * The eXtended-nonce ChaCha cipher construction (XChaCha) allows for 033 * ChaCha-based cipher suites to accept a 192-bit nonce with similar guarantees 034 * to the original construction, except with a much lower probability of nonce 035 * misuse occurring. 036 * 037 * <p>This class is thread-safe. 038 * 039 * @see <a href="https://datatracker.ietf.org/doc/html/draft-arciszewski-xchacha-03">XChaCha: 040 * eXtended-nonce ChaCha and AEAD_XChaCha20_Poly1305</a> 041 * 042 * 043 * @author Alexander Martynov 044 * @version 2021-08-04 045 */ 046@ThreadSafe 047public class XC20P { 048 049 /** 050 * The standard authentication tag length (128 bits). 051 */ 052 public static final int AUTH_TAG_BIT_LENGTH = 128; 053 054 /** 055 * The standard Initialisation Vector (IV) length (192 bits). 056 */ 057 public static final int IV_BIT_LENGTH = 192; 058 059 060 /** 061 * Encrypts the specified plain text using XChaCha20_Poly1305. 062 * 063 * @param secretKey The AES key. Must not be {@code null}. 064 * @param plainText The plain text. Must not be {@code null}. 065 * @param ivContainer The initialisation vector (IV). 066 * This is output parameter. On output, it carries 067 * the nonce the cipher actually used. 068 * @param authData The authenticated data. Must not be {@code null}. 069 * 070 * @return The authenticated cipher text. 071 * 072 * @throws JOSEException If encryption failed. 073 */ 074 public static AuthenticatedCipherText encryptAuthenticated(final SecretKey secretKey, 075 final Container<byte[]> ivContainer, 076 final byte[] plainText, 077 final byte[] authData) 078 throws JOSEException { 079 080 final XChaCha20Poly1305 aead; 081 082 try { 083 aead = new XChaCha20Poly1305(secretKey.getEncoded()); 084 085 } catch (InvalidKeyException e) { 086 throw new JOSEException("Invalid XChaCha20Poly1305 key: " + e.getMessage(), e); 087 } 088 089 final byte[] cipherOutput; 090 091 try { 092 cipherOutput = aead.encrypt(plainText, authData); 093 094 } catch (GeneralSecurityException e) { 095 throw new JOSEException("Couldn't encrypt with XChaCha20Poly1305: " + e.getMessage(), e); 096 } 097 098 final int tagPos = cipherOutput.length - ByteUtils.byteLength(AUTH_TAG_BIT_LENGTH); 099 final int cipherTextPos = ByteUtils.byteLength(IV_BIT_LENGTH); 100 101 byte[] iv = ByteUtils.subArray(cipherOutput, 0, cipherTextPos); 102 byte[] cipherText = ByteUtils.subArray(cipherOutput, cipherTextPos, tagPos - cipherTextPos); 103 byte[] authTag = ByteUtils.subArray(cipherOutput, tagPos, ByteUtils.byteLength(AUTH_TAG_BIT_LENGTH)); 104 105 // set nonce 106 ivContainer.set(iv); 107 108 return new AuthenticatedCipherText(cipherText, authTag); 109 } 110 111 112 /** 113 * Decrypts the specified cipher text using XChaCha20_Poly1305. 114 * 115 * @param secretKey The AES key. Must not be {@code null}. 116 * @param iv The initialisation vector (IV). Must not be 117 * {@code null}. 118 * @param cipherText The cipher text. Must not be {@code null}. 119 * @param authData The authenticated data. Must not be {@code null}. 120 * @param authTag The authentication tag. Must not be {@code null}. 121 * 122 * @return The decrypted plain text. 123 * 124 * @throws JOSEException If decryption failed. 125 */ 126 public static byte[] decryptAuthenticated(final SecretKey secretKey, 127 final byte[] iv, 128 final byte[] cipherText, 129 final byte[] authData, 130 final byte[] authTag) 131 throws JOSEException { 132 133 final XChaCha20Poly1305 aead; 134 135 try { 136 aead = new XChaCha20Poly1305(secretKey.getEncoded()); 137 138 } catch (InvalidKeyException e) { 139 throw new JOSEException("Invalid XChaCha20Poly1305 key: " + e.getMessage(), e); 140 } 141 142 final byte[] cipherInput = ByteUtils.concat(iv, cipherText, authTag); 143 144 try { 145 return aead.decrypt(cipherInput, authData); 146 147 } catch (GeneralSecurityException e) { 148 149 throw new JOSEException("XChaCha20Poly1305decryption failed: " + e.getMessage(), e); 150 } 151 } 152}