Package com.google.nigori.client

Source Code of com.google.nigori.client.RealKeyManager

/*
* Copyright (C) 2011 Google Inc. Copyright (C) 2011 Alastair R. Beresford
*
* 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 com.google.nigori.client;

import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.Arrays;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

import com.google.nigori.common.DSASign;
import com.google.nigori.common.MessageLibrary;
import com.google.nigori.common.NigoriConstants;
import com.google.nigori.common.NigoriCryptographyException;
import com.google.nigori.common.Util;

/**
* Manages the set of keys derived from a given (servername, username and password) triple.
*
* @author Alastair Beresford
*/
public class RealKeyManager implements KeyManager {

  /**
   * For authenticating the user
   */
  private byte[] userSecretKey;
  /**
   * For encrypting the data
   */
  private byte[] encryptionSecretKey;
  /**
   * For HMACs on ciphertexts
   */
  private byte[] macSecretKey;
  /**
   * For HMACs on plaintexts, used for initialisation vectors
   */
  private byte[] ivSecretKey;

  private byte[] username;
  private byte[] password;
  private String serverName;

  private final SecureRandom random = new SecureRandom();
  private final PasswordGenerator pwgen = new PasswordGenerator();

  /**
   * Given a ({@code servername}, {@code username}, {@code password}) triple, generate Nigori keys.
   *
   * @param servername the domain name of the server used to store data.
   * @param username the registered username of the user at {@code servername}.
   * @param password the password of {@code username} at {@code servername}.
   * @throws NigoriCryptographyException
   */
  public RealKeyManager(String serverName, byte[] username, byte[] password)
      throws NigoriCryptographyException {

    initialiseKeys(serverName, username, password);
  }

  /**
   * Given a {@code servername}, auto-generate a username and password, then generate Nigori keys.
   *
   * @param servername the domain name of the server used to store data.
   * @throws NigoriCryptographyException
   */
  public RealKeyManager(String serverName) throws NigoriCryptographyException {
    initialiseKeys(serverName, pwgen.generate(), pwgen.generate());
  }

  private void initialiseKeys(String serverName, byte[] username, byte[] password)
      throws NigoriCryptographyException {

    this.username = username;
    this.password = password;
    this.serverName = serverName;

    byte[] userAndServer = Util.joinBytes(username, MessageLibrary.toBytes(serverName));
    byte[] salt =
        pbkdf2(userAndServer, NigoriConstants.USER_SALT, NigoriConstants.N_SALT,
            NigoriConstants.B_SUSER);

    this.userSecretKey = pbkdf2(password, salt, NigoriConstants.N_USER, NigoriConstants.B_DSA);
    this.encryptionSecretKey =
        pbkdf2(password, salt, NigoriConstants.N_ENC, NigoriConstants.B_KENC);
    this.macSecretKey = pbkdf2(password, salt, NigoriConstants.N_MAC, NigoriConstants.B_KMAC);
    this.ivSecretKey = pbkdf2(password, salt, NigoriConstants.N_IV, NigoriConstants.B_KMAC);
  }

  protected static byte[] pbkdf2(byte[] password, byte[] salt, int rounds, int outputByteCount)
      throws NigoriCryptographyException {

    // Standard Java PBKDF2 takes the lower 8 bits of each element of a char array as input.
    // Therefore, rewrite byte array into char array, preserving lower 8-bit pattern
    char[] charPassword = new char[password.length];
    for (int i = 0; i < charPassword.length; i++) {
      if (password[i] >= 0) {
        charPassword[i] = (char) password[i];
      } else {
        charPassword[i] = (char) (password[i] + 256); // cope with signed -> unsigned
      }
    }
    try {
      SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
      KeySpec spec = new PBEKeySpec(charPassword, salt, rounds, 8 * outputByteCount);
      return factory.generateSecret(spec).getEncoded();
    } catch (NoSuchAlgorithmException e) {
      throw new NigoriCryptographyException(e);
    } catch (InvalidKeySpecException e) {
      throw new NigoriCryptographyException(e);
    }
  }

  private byte[] generateCipherHMAC(byte[] message) throws NigoriCryptographyException {
    return generateHMAC(message, macSecretKey);
  }

  private byte[] generatePlaintextHMAC(byte[] message) throws NigoriCryptographyException {
    return generateHMAC(message, ivSecretKey);
  }

  private byte[] generateHMAC(byte[] message, byte[] secretKey) throws NigoriCryptographyException {

    try {
      String hmacAlgorithm = NigoriConstants.A_HMAC;
      Mac mac = Mac.getInstance(hmacAlgorithm);
      SecretKey key = new SecretKeySpec(secretKey, NigoriConstants.A_KMAC);
      mac.init(key);
      return mac.doFinal(message);
    } catch (Exception e) {
      throw new NigoriCryptographyException(e);
    }
  }

  @Override
  public byte[] getUsername() {
    return username.clone();
  }

  @Override
  public byte[] getPassword() {
    return password.clone();
  }

  @Override
  public String getServerName() {
    return serverName;
  }

  @Override
  public byte[] decrypt(byte[] ciphertext) throws NigoriCryptographyException {
    return decrypt(encryptionSecretKey, ciphertext);
  }

  /**
   * Use this object's keys to decrypt {@code ciphertext} and return plaintext.
   *
   * This method expects the IV to be stored in the first {@link NigoriConstants#B_AES} bytes and a
   * MAC to be stored in the final {@link NigoriConstants#B_MAC} bytes.
   *
   * @param ciphertext the message to decrypt.
   *
   * @throws NigoriCryptographyException if total length of message <48 bytes, if the MAC does not
   *           match the decoded data, or if something goes wrong with AES/CBC/PKCS5Padding inside
   *           the JCE library.
   */
  @Override
  public byte[] decrypt(byte[] encryptionKey, byte[] ciphertext) throws NigoriCryptographyException {

    byte[] iv = new byte[NigoriConstants.B_SYMENC];
    byte[] mac = new byte[NigoriConstants.B_MAC];
    Cipher cipher;
    try {
      cipher = Cipher.getInstance(NigoriConstants.A_SYMENC_CIPHER);
    } catch (NoSuchPaddingException e) {
      throw new NigoriCryptographyException(e);
    } catch (NoSuchAlgorithmException e) {
      throw new NigoriCryptographyException(e);
    }

    if (ciphertext.length < iv.length + mac.length + cipher.getBlockSize()) {
      throw new NigoriCryptographyException(
          "Ciphertext is too short to be a valid encrypted message.");
    }

    byte[] data = new byte[ciphertext.length - iv.length - mac.length];
    System.arraycopy(ciphertext, 0, iv, 0, iv.length);
    System.arraycopy(ciphertext, ciphertext.length - mac.length, mac, 0, mac.length);
    System.arraycopy(ciphertext, iv.length, data, 0, data.length);

    byte[] macCheck = generateCipherHMAC(data);
    if (mac.length != macCheck.length) {
      throw new NigoriCryptographyException(String.format(
          "Length mismatch between provided (%d) and received (%d) HMACs.", mac.length,
          macCheck.length));
    }

    for (int i = 0; i < macCheck.length; i++) {
      if (mac[i] != macCheck[i]) {
        throw new NigoriCryptographyException("HMAC of ciphertext does not match expected value");
      }
    }

    try {
      SecretKey key = new SecretKeySpec(encryptionKey, NigoriConstants.A_SYMENC);
      cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
      return cipher.doFinal(data);
    } catch (InvalidAlgorithmParameterException e) {
      throw new NigoriCryptographyException(e);
    } catch (InvalidKeyException e) {
      throw new NigoriCryptographyException(e);
    } catch (BadPaddingException e) {
      throw new NigoriCryptographyException(e);
    } catch (IllegalBlockSizeException e) {
      throw new NigoriCryptographyException(e);
    }
  }

  /**
   * Encrypted {@code plaintext} with AES using a random IV.
   *
   * @param plaintext The message to encrypt.
   *
   * @throws NigoriCryptographyException
   */
  @Override
  public byte[] encrypt(byte[] plaintext) throws NigoriCryptographyException {
    return encrypt(encryptionSecretKey, plaintext, true);
  }

  @Override
  public byte[] encrypt(byte[] key, byte[] plaintext) throws NigoriCryptographyException {
    return encrypt(key, plaintext, true);
  }

  @Override
  public byte[] encryptDeterministically(byte[] plaintext) throws NigoriCryptographyException {
    return encrypt(encryptionSecretKey, plaintext, false);
  }

  @Override
  public byte[] encryptDeterministically(byte[] key, byte[] plaintext)
      throws NigoriCryptographyException {
    return encrypt(key, plaintext, false);
  }

  private byte[] encrypt(byte[] key, byte[] plaintext, boolean randomIV)
      throws NigoriCryptographyException {

    try {
      Cipher cipher = Cipher.getInstance(NigoriConstants.A_SYMENC_CIPHER);
      SecretKey secret = new SecretKeySpec(key, NigoriConstants.A_SYMENC);
      byte[] iv = new byte[NigoriConstants.B_SYMENC];
      if (randomIV) {
        random.nextBytes(iv);
      } else {
        byte[] ivMac = generatePlaintextHMAC(plaintext);
        xorFill(iv, ivMac);
      }
      IvParameterSpec ips = new IvParameterSpec(iv);
      cipher.init(Cipher.ENCRYPT_MODE, secret, ips);
      byte[] data = cipher.doFinal(plaintext);
      byte[] mac = generateCipherHMAC(data);

      byte[] ciphertext = new byte[iv.length + data.length + mac.length];
      System.arraycopy(iv, 0, ciphertext, 0, iv.length);
      System.arraycopy(data, 0, ciphertext, iv.length, data.length);
      System.arraycopy(mac, 0, ciphertext, iv.length + data.length, mac.length);

      return ciphertext;
    } catch (NoSuchPaddingException e) {
      throw new NigoriCryptographyException(e);
    } catch (NoSuchAlgorithmException e) {
      throw new NigoriCryptographyException(e);
    } catch (InvalidAlgorithmParameterException e) {
      throw new NigoriCryptographyException(e);
    } catch (InvalidKeyException e) {
      throw new NigoriCryptographyException(e);
    } catch (BadPaddingException e) {
      throw new NigoriCryptographyException(e);
    } catch (IllegalBlockSizeException e) {
      throw new NigoriCryptographyException(e);
    }
  }

  /**
   * @param iv
   * @param ivMac
   */
  private void xorFill(byte[] iv, byte[] ivMac) {
    for (int macIdx = 0, ivIdx = 0; macIdx < ivMac.length; ++macIdx, ++ivIdx, ivIdx %= iv.length) {
      iv[ivIdx] ^= ivMac[macIdx];
    }
  }

  /**
   * Return an instance of {@code SchnorrSign} which is capable of signing user-encrypted data.
   *
   * @throws NoSuchAlgorithmException
   *
   */
  @Override
  public DSASign signer() throws NigoriCryptographyException {
    try {
      return new DSASign(userSecretKey);
    } catch (NoSuchAlgorithmException e) {
      throw new NigoriCryptographyException(e);
    }
  }

  /**
   * Destroy all the secret data stored in this KeyManager
   */
  public void destroy() {
    Arrays.fill(userSecretKey, (byte) 0);
    Arrays.fill(encryptionSecretKey, (byte) 0);
    Arrays.fill(macSecretKey, (byte) 0);
    Arrays.fill(ivSecretKey, (byte) 0);
    Arrays.fill(username, (byte) 0);
    Arrays.fill(password, (byte) 0);
  }

  @Override
  public void finalize() throws Throwable {
    destroy();
    super.finalize();
  }
}
TOP

Related Classes of com.google.nigori.client.RealKeyManager

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.