Шифрование связи между телефоном и сервером в Java - PullRequest
1 голос
/ 21 сентября 2009

Это идет вместе с другим потоком, здесь: Как реализовать 256-битное Java AES-шифрование с CBC

В основном, пытаясь получить шифрование AES на телефоне с помощью Java.

Мой вопрос здесь заключается в том, как обращаться с ключом шифрования. Я не знаю, должен ли я хранить ключ, хешировать ключ и использовать его или делать схему шифрования с открытым ключом. Я предпочел бы не делать начальное сообщение с сервера на телефон для передачи ключа. Я хочу, чтобы телефон был готов к шифрованию, а сервер ожидал зашифрованного сообщения. Чтобы реализовать что-то вроде алгоритма совместного использования ключей, мне пришлось бы модифицировать наше серверное приложение, что не очень желательно. Это не невозможно, но я собираюсь повторить код здесь =).

1 Ответ

1 голос
/ 21 сентября 2009

Пытаетесь ли вы найти способ хранить один и тот же ключ на каждом телефоне, но помешать злоумышленникам получить ключ и совершать с ним плохие действия? Это просто невозможно.

Оцените, сколько это может потенциально стоить, если ключ будет раскрыт. Затем оцените стоимость понимания и реализации чего-то вроде следующей схемы соглашения о ключах. Выберите альтернативу, которая наилучшим образом соответствует вашим целям.

Используйте эфемерное & ndash; статическое соглашение ключа Диффи-Хеллмана. Чтобы перехватить трафик, злоумышленнику придется взломать определенный телефон и заменить открытый ключ сервера, который встроен в приложение, своим собственным. Это довольно сложно, и каждый пользователь должен быть нацелен на каждого, поэтому атака плохо масштабируется.

Также будет работать шифрование RSA. Это безопасно и широко поддерживается. Но у эфемерного Диффи-Хеллмана есть одно преимущество. RSA будет полагаться на фиксированную пару ключей, поэтому, если злоумышленник в конечном итоге восстановит закрытый ключ, он сможет расшифровать любые ранее записанные сообщения.

Поскольку скорость двух алгоритмов должна быть сопоставимой, я рекомендую Диффи-Хеллмана, если телефоны не поддерживают ее. Вот пример. (Прошу прощения за чрезмерную длину.)

import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;

import javax.crypto.Cipher;
import javax.crypto.KeyAgreement;
import javax.crypto.interfaces.DHPublicKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class DHExample
{

  public static void main(String... argv)
    throws Exception
  {
    if (argv.length == 0)
      argv = new String[]{"swright,password,transfer(10000USD,erickson)"};

    /* In reality, the private key should be stored in a KeyStore, protected by 
     * a strong password that is provided interactively every time the server 
     * is started.
     */
    PrivateKey pvt = Server.loadDemoKey();
    Server server = new Server(pvt);

    for (String message : argv) {
      Client client = new Client();
      byte[] packet = client.encrypt(message);
      /* The client (phone) would then send the packet to the server... */
      /* ... Now, at the server: */
      System.out.println(server.decrypt(packet));
    }

  }

}

class Client
{

  /* This public key corresponds to the private key used by the server. 
   * Generate your own Diffie-Hellman key pair, encode the public key, and 
   * embed it here.
   */
  private static final String server = "1GG80QCC4204DGC29AGP48DTOD041G2C42046050C103UNUKS13LQH4AAIRT59OBNCSJJVC4DNA8UEUH00OCF3V05MA4J6IHAT80H53UQP7M6LHULVONQRKC7MPEDLAR6NG4TO079KDVP6CO5NDECL19D4JUFUG13R20HC4JTRL7BVTDU63FS3MLV7OQKAC58F0JTO7TMJOKFC60HLAG9LK5KH6BR7BSTE5DGTEANFU8H066CTQ5403HO2G60G1TV1K22TD6PTRR5RPAQS6QS5FEBPIINRNUHQTA1FILQC1CUGF0J7A5CLF3LQQHCKVPJH0S88305K94B728V89GK1C4TNPS4J5368KRGJO5JQHDA7P398S2HQS7HBMEJ7B4BEKDVGNUH16LHF3UR2F80I8EUCKJORTA2HI24QH0UVS5DEB7O6IA5MCNK0FDAIAP019GTVTJQ9581040G00E0O8002G600TNIVRQ4KUFKVHTM685L9LRNURUUJ8B1EC45DN5TM70MI0T8RM17QCBSQ2F1FMQM3K63MVVQBMVD7N5MRVMGRVM09B1LGNU92SMS42RSROH3FMP4532CMDHO6E72ICI7USNGGDHPFLTSS7AAFRM9T8E6ELU4E3P5EBA6JPDRUN0PDNG08HBVSRJM5U7VVAQ1PD8FIJMSN9EM3";

  public byte[] encrypt(String message)
    throws Exception
  {
    /* Convert the embedded server public key into a DHPublickKey object. */
    KeyFactory kf = KeyFactory.getInstance("DiffieHellman");
    byte[] decoded = new BigInteger(Client.server, 32).toByteArray();
    DHPublicKey server = (DHPublicKey) kf.generatePublic(new X509EncodedKeySpec(decoded));
    /* Generate an ephemeral key pair with the same parameters as server's. */
    KeyPairGenerator gen = KeyPairGenerator.getInstance("DiffieHellman");
    gen.initialize(server.getParams());
    KeyPair ephemeral = gen.generateKeyPair();
    /* Encode the client public key to be sent to server with ciphertext. */
    byte[] pub = ephemeral.getPublic().getEncoded();
    /* Generate a secret key using Diffie-Hellman between client and server. */
    KeyAgreement ka = KeyAgreement.getInstance("DiffieHellman");
    ka.init(ephemeral.getPrivate());
    ka.doPhase(server, true);
    byte[] raw = ka.generateSecret();
    SecretKeySpec secret;
    try {
      secret = new SecretKeySpec(raw, 0, 16, "AES");
    }
    finally {
      Arrays.fill(raw, (byte) 0);
    }
    /* Setup cipher for encryption with secret key. */
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    cipher.init(Cipher.ENCRYPT_MODE, secret);
    /* Get the IV to be sent to server with ciphertext. */
    byte[] iv = cipher.getParameters().getParameterSpec(IvParameterSpec.class).getIV();
    /* Encrypt the message with the secret key. */
    byte[] plaintext = message.getBytes("UTF-8");
    byte[] ciphertext;
    try {
      ciphertext = cipher.doFinal(plaintext);
    }
    finally {
      Arrays.fill(plaintext, (byte) 0);
    }
    /* Package up ephemeral public key, iv, and cipher text to transmit to server. */
    byte[] packet = new byte[4 + pub.length + iv.length + ciphertext.length];
    for (int idx = 0; idx < 4; ++idx)
      packet[idx] = (byte) (pub.length >>> 8 * idx);
    System.arraycopy(pub, 0, packet, 4, pub.length);
    System.arraycopy(iv, 0, packet, 4 + pub.length, iv.length);
    System.arraycopy(ciphertext, 0, packet, 4 + pub.length + iv.length, ciphertext.length);
    return packet;
  }


}

class Server
{

  /* This key isn't private anymore, since it was posted on stackoverflow.com.
   * Generate your own Diffie-Hellman key pair, encode the private key, and 
   * embed it here. In real code, you don't embed private keys like this. This 
   * is just to enable the demonstration.
   */
  private static final String demo = "310G1CS10201GG80HM1G95A34H1NN1K0G609GG80GO0K1G40FQVQJG4ENA4H9ABFKL71ETJIEFTGHMT93PRQ4031HSFS0MP8ICQA5BL024KFRB4UOQM7QNV2VBEHGUR5PMLBCQU0JN00T6HNV4PJ0MTLPIK55KIFPVQ04FC825GIFNEKTFVLNOODVGEQNSV3AH9GL1S2FN0VMQF2HTGO26LA16MGMI4PFCTFJLOLM3LPATVP240OPJN8KG0E70A0O207NS6G8BLKR7NFCNF5BBGRBGLTPF6AAVEVQ7BL85UAN9G5JQ1S2CT8LILSENBA5IJV6E43H10C0MH4HCS93T162G5GJMV7GICKCP2JE2F0MFA5L8V4D53GA7BGU5EPQCTCHDQHNU2VQ44QM5SFRC9T0291RPIIF3FL8A688JA43RVGLLPCV0Q98MPIUG1TLA9B40563NVMF94L040G2002460I103FO2B2UAKGMNI68O6B43G31SRIBA2NGIV0BEUVD4A85NIC25AVUGP3AG6LJVFHPOJ4JQ3DJA0PFHA2V6E2U80HNHFDG157KP0NJLE7U";

  private final PrivateKey pvt;

  static PrivateKey loadDemoKey()
    throws Exception
  {
    /* Convert the embedded server public key into a PrivateKey object. */
    KeyFactory kf = KeyFactory.getInstance("DiffieHellman");
    byte[] decoded = new BigInteger(Server.demo, 32).toByteArray();
    return kf.generatePrivate(new PKCS8EncodedKeySpec(decoded));
  }

  Server(PrivateKey pvt)
  {
    this.pvt = pvt;
  }

  public String decrypt(byte[] packet)
    throws Exception
  {
    /* Deconstruct packet. */
    int len = 0;
    for (int idx = 0; idx < 4; ++idx)
      len |= ((packet[idx] & 0xFF) << 8 * idx);
    byte[] pub = new byte[len];
    System.arraycopy(packet, 4, pub, 0, len);
    KeyFactory kf = KeyFactory.getInstance("DiffieHellman");
    PublicKey client = kf.generatePublic(new X509EncodedKeySpec(pub));
    IvParameterSpec iv = new IvParameterSpec(packet, len + 4, 16);
    /* Perform key agreement to determine secret key. */
    KeyAgreement ka = KeyAgreement.getInstance("DiffieHellman");
    ka.init(pvt);
    ka.doPhase(client, true);
    byte[] raw = ka.generateSecret();
    SecretKeySpec secret;
    try {
      secret = new SecretKeySpec(raw, 0, 16, "AES");
    }
    finally {
      Arrays.fill(raw, (byte) 0);
    }
    /* Setup cipher for decryption with secret key. */
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    cipher.init(Cipher.DECRYPT_MODE, secret, iv);
    byte[] plaintext = cipher.doFinal(packet, len + 20, packet.length - (len + 20));
    try {
      return new String(plaintext, "UTF-8");
    }
    finally {
      Arrays.fill(plaintext, (byte) 0);
    }
  }

}
...