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

fr.acinq.eclair.crypto.ChaCha20Poly1305.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2019 ACINQ SAS
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package fr.acinq.eclair.crypto

import java.nio.ByteOrder

import fr.acinq.bitcoin.{ByteVector32, Protocol}
import grizzled.slf4j.Logger
import grizzled.slf4j.Logging
import org.spongycastle.crypto.engines.ChaCha7539Engine
import org.spongycastle.crypto.params.{KeyParameter, ParametersWithIV}
import scodec.bits.ByteVector

/**
  * Poly1305 authenticator
  * see https://tools.ietf.org/html/rfc7539#section-2.5
  */
object Poly1305 {
  /**
    *
    * @param key   input key
    * @param datas input data
    * @return a 16 byte authentication tag
    */
  def mac(key: ByteVector, datas: ByteVector*): ByteVector = {
    val out = new Array[Byte](16)
    val poly = new org.spongycastle.crypto.macs.Poly1305()
    poly.init(new KeyParameter(key.toArray))
    datas.foreach(data => poly.update(data.toArray, 0, data.length.toInt))
    poly.doFinal(out, 0)
    ByteVector.view(out)
  }
}

/**
  * ChaCha20 block cipher
  * see https://tools.ietf.org/html/rfc7539#section-2.5
  */
object ChaCha20 {
  // Whenever key rotation happens, we start with a nonce value of 0 and increment it for each message.
  val ZeroNonce = ByteVector.fill(12)(0.byteValue)

  def encrypt(plaintext: ByteVector, key: ByteVector, nonce: ByteVector, counter: Int = 0): ByteVector = {
    val engine = new ChaCha7539Engine()
    engine.init(true, new ParametersWithIV(new KeyParameter(key.toArray), nonce.toArray))
    val ciphertext: Array[Byte] = new Array[Byte](plaintext.length.toInt)
    counter match {
      case 0 => ()
      case 1 =>
        // skip 1 block == set counter to 1 instead of 0
        val dummy = new Array[Byte](64)
        engine.processBytes(new Array[Byte](64), 0, 64, dummy, 0)
      case _ => throw new RuntimeException(s"chacha20 counter must be 0 or 1")
    }
    val len = engine.processBytes(plaintext.toArray, 0, plaintext.length.toInt, ciphertext, 0)
    require(len == plaintext.length, "ChaCha20 encryption failed")
    ByteVector.view(ciphertext)
  }

  def decrypt(ciphertext: ByteVector, key: ByteVector, nonce: ByteVector, counter: Int = 0): ByteVector = {
    val engine = new ChaCha7539Engine
    engine.init(false, new ParametersWithIV(new KeyParameter(key.toArray), nonce.toArray))
    val plaintext: Array[Byte] = new Array[Byte](ciphertext.length.toInt)
    counter match {
      case 0 => ()
      case 1 =>
        // skip 1 block == set counter to 1 instead of 0
        val dummy = new Array[Byte](64)
        engine.processBytes(new Array[Byte](64), 0, 64, dummy, 0)
      case _ => throw new RuntimeException(s"chacha20 counter must be 0 or 1")
    }
    val len = engine.processBytes(ciphertext.toArray, 0, ciphertext.length.toInt, plaintext, 0)
    require(len == ciphertext.length, "ChaCha20 decryption failed")
    ByteVector.view(plaintext)
  }
}

/**
  * ChaCha20Poly1305 AEAD (Authenticated Encryption with Additional Data) algorithm
  * see https://tools.ietf.org/html/rfc7539#section-2.5
  *
  * This what we should be using (see BOLT #8)
  */
object ChaCha20Poly1305 extends Logging {

  // This logger is used to dump encryption keys to enable traffic analysis by the lightning-dissector.
  // See https://github.com/nayutaco/lightning-dissector for more details.
  // It is disabled by default (in the logback.xml configuration file).
  val keyLogger = Logger("keylog")

  /**
    *
    * @param key       32 bytes encryption key
    * @param nonce     12 bytes nonce
    * @param plaintext plain text
    * @param aad       additional authentication data. can be empty
    * @return a (ciphertext, mac) tuple
    */
  def encrypt(key: ByteVector, nonce: ByteVector, plaintext: ByteVector, aad: ByteVector): (ByteVector, ByteVector) = {
    val polykey = ChaCha20.encrypt(ByteVector32.Zeroes, key, nonce)
    val ciphertext = ChaCha20.encrypt(plaintext, key, nonce, 1)
    val tag = Poly1305.mac(polykey, aad, pad16(aad), ciphertext, pad16(ciphertext), Protocol.writeUInt64(aad.length, ByteOrder.LITTLE_ENDIAN), Protocol.writeUInt64(ciphertext.length, ByteOrder.LITTLE_ENDIAN))

    logger.debug(s"encrypt($key, $nonce, $aad, $plaintext) = ($ciphertext, $tag)")
    if (nonce === ChaCha20.ZeroNonce) {
      keyLogger.debug(s"${tag.toHex} ${key.toHex}")
    }

    (ciphertext, tag)
  }

  /**
    *
    * @param key        32 bytes decryption key
    * @param nonce      12 bytes nonce
    * @param ciphertext ciphertext
    * @param aad        additional authentication data. can be empty
    * @param mac        authentication mac
    * @return the decrypted plaintext if the mac is valid.
    */
  def decrypt(key: ByteVector, nonce: ByteVector, ciphertext: ByteVector, aad: ByteVector, mac: ByteVector): ByteVector = {
    val polykey = ChaCha20.encrypt(ByteVector32.Zeroes, key, nonce)
    val tag = Poly1305.mac(polykey, aad, pad16(aad), ciphertext, pad16(ciphertext), Protocol.writeUInt64(aad.length, ByteOrder.LITTLE_ENDIAN), Protocol.writeUInt64(ciphertext.length, ByteOrder.LITTLE_ENDIAN))
    require(tag == mac, "invalid mac")
    val plaintext = ChaCha20.decrypt(ciphertext, key, nonce, 1)

    logger.debug(s"decrypt($key, $nonce, $aad, $ciphertext, $mac) = $plaintext")
    if (nonce === ChaCha20.ZeroNonce) {
      keyLogger.debug(s"${mac.toHex} ${key.toHex}")
    }

    plaintext
  }

  def pad16(data: ByteVector): ByteVector =
    if (data.size % 16 == 0)
      ByteVector.empty
    else
      ByteVector.fill(16 - (data.size % 16))(0)
}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy