it-swarm-es.com

¿Cuál es la forma estándar de cifrar un archivo con una clave pública en Java?

Estoy escribiendo una aplicación cliente en Java que envía datos encriptados de vuelta al servidor. Todos los clientes tienen mi clave pública RSA. ¿Existe una forma estándar de encriptar un archivo dado para enviarlo? de vuelta al servidor?

De sitios web como este , deduzco que generalmente, se genera una clave simétrica para cifrar el archivo real, y esta clave se cifra con la clave pública RSA y se envía junto con el archivo. ¿Existe una forma estándar en Java o un formato de archivo estándar (sin bibliotecas adicionales) para empaquetar la clave simétrica cifrada y el archivo cifrado simétricamente?

Por supuesto, podría diseñar mi propio formato de archivo, pero preferiría apegarme a los estándares si es posible.

15

Un formato de cifrado estándar común es la sintaxis de mensajes criptográficos (CMS, desarrollado a partir de PKCS # 7), y p. BouncyCastle lo admite. También permite firmas y es la base, por ejemplo, para la seguridad de los mensajes de correo S/MIME. Puede ser excesivo, pero es ampliamente compatible.

El código de ejemplo lo proporciona Bouncy Castle. Para CMS, vea org.bouncycastle.cms.test , probablemente los ejemplos de datos envueltos.

Otra opción es el formato de cifrado XML, estandarizado por W3C. Para ejemplos Java ejemplos, vea, por ejemplo, Explorando el cifrado XML, Parte 1 o hágalo con XSS4J: Implementando cifrado XML en Java

8
nealmcb

La forma más simple y segura de cifrar archivos es hacer Shell e invocar gpg o pgp para cifrarlo. Sé que suena poco elegante, pero en mi experiencia revisando mucho código criptográfico, esta es la forma más simple de garantizar que su solución sea segura, sin tener que saber mucho sobre criptografía. Si desea hacerlo usted mismo, necesitará aprender más sobre cómo funciona la criptografía y aumenta el riesgo de un problema de seguridad.

Asegúrese de que haya protección de autenticidad para los datos encriptados (por ejemplo, encriptar y luego firmar/MAC): necesitará tanto protección de confidencialidad como de autenticidad. Un error muy común es encriptar pero no firmar; Este error puede introducir problemas sutiles de seguridad. (Si no es un criptógrafo, me doy cuenta de que esto puede no tener sentido para usted. Me doy cuenta de que esto no es obvio, pero soy un criptógrafo. Si está familiarizado con IND-CCA2 e INT-CTXT, comprenderá por qué estoy recomendando esto. Si no está familiarizado con esos conceptos, puede leer sobre ellos si lo desea, o puede tomar mi palabra. Hola, la criptografía es algo complicado; ¿qué puedo decir?) Por ejemplo, el sitio web al que enlaza comete este error; No debería confiar en ese sitio web para obtener consejos sobre el cifrado seguro. El enfoque de hombre de paja que menciona en su pregunta también adolece de este defecto, y parece que algunas de las respuestas propuestas aquí también tienen una deficiencia similar.

Un enfoque alternativo, que es muy bueno si el cliente y el servidor están conectados en tiempo real, es abrir un canal cifrado TLS entre el cliente y el servidor y enviar los datos a través de ese canal. Asegúrese de que el cliente verifique la clave pública/certificado del servidor. Si desea autenticar clientes, déles un certificado de cliente y asegúrese de que el servidor requiera que el cliente proporcione un certificado de cliente y valide dicho certificado.

3
D.W.

@JPP, este comentario tuyo me hace pensar que estás haciendo la pregunta incorrecta:

La aplicación cliente guarda los registros de actividad del usuario cuyo contenido debe publicarse en un servidor sin modificaciones, incluso si el usuario tiene acceso de lectura/escritura a los archivos guardados. Además, el usuario no debería poder inspeccionar el contenido (los archivos son informes de ejercicio, progreso y error de una aplicación de prueba).

Si la aplicación cliente escribe los archivos de registro y los cifra, eso significa que la aplicación cliente tiene acceso a la clave de cifrado, sea lo que sea.
¡Sin embargo, está tratando de evitar que el usuario de la aplicación cliente lea los archivos!
Eso simplemente no va a funcionar, porque:

  1. El usuario tiene acceso a cualquier cosa a la que tenga acceso la aplicación cliente, incluidos el contenido del archivo y la clave de cifrado ...
  2. El usuario tiene el control de cualquier cosa que se ejecute en su propio proceso, es decir, incluso puede adjuntar un depurador a la aplicación del cliente, así que no se moleste en tratar de ser inteligente y encontrar una manera de ofuscar la clave de cifrado.

Para mí, su problema se parece más al control de acceso: ¿cómo puedo escribir un archivo de registro desde una aplicación cliente sin que el usuario tenga acceso a los contenidos?
Una forma sería crear un servicio local (por ejemplo, Servicio de Windows, en Windows) que se ejecute en un contexto de usuario diferente; la aplicación cliente hablaría con el servicio, y el servicio luego escribiría el contenido en el archivo en una ubicación protegida a la que los usuarios normales no puedan acceder.
Otra alternativa es, como @ D.W. sugerido, envíelo directamente al servidor de inmediato y no escriba en el archivo ninguno de los contenidos.

El problema con estas dos soluciones es como señalé anteriormente: el usuario tiene el control de cualquier cosa en su proceso y puede bloquear o modificar cualquier solicitud enviada, ya sea al servidor o al servicio local de Windows.

3
AviD

Aparentemente, no hay forma de hacerlo con la biblioteca estándar sin una recodificación significativa de cosas ya disponibles en Bouncy Castle, por ejemplo.

Esto es lo que se me ocurrió. En el lado del cliente, cifro un archivo plainfile de la siguiente manera:

// install security provider
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
    Security.addProvider(new BouncyCastleProvider());
}

// load public certificate for signing
KeyStore publicKs = KeyStore.getInstance(KeyStore.getDefaultType());
publicKs.load(new FileInputStream("/path/to/publicKeystore.ks"), null);
TrustedCertificateEntry entry = (TrustedCertificateEntry) publicKs.getEntry("public", null);
X509Certificate cert = (X509Certificate) entry.getTrustedCertificate();

// create CMS envelope data;
// check http://www.ietf.org/rfc/rfc3852.txt pages 15-16 for details
CMSEnvelopedDataGenerator envelopedDataGen = new CMSEnvelopedDataGenerator();

// specify that generated symmetric key will be encrypted by with this public key
envelopedDataGen.addKeyTransRecipient(cert);

// automatically generate the AES key, encrypt the data, and encrypt the key
CMSEnvelopedData data = envelopedDataGen.generate(new CMSProcessableFile(plainfile),
        CMSEnvelopedDataGenerator.AES256_CBC, 256, BouncyCastleProvider.PROVIDER_NAME);

byte[] encryptedData = data.getEncoded();

Luego, en el servidor, puedo descifrar estos datos de la siguiente manera:

// load private key
KeyStore privateKs = KeyStore.getInstance(KeyStore.getDefaultType());
privateKs.load(new FileInputStream("/path/to/privateKeystore.ks"), null);
PrivateKeyEntry privateKeyEntry = (PrivateKeyEntry) privateKs.getEntry("privatekey",
        new KeyStore.PasswordProtection("password".toCharArray()));
PrivateKey privateKey = privateKeyEntry.getPrivateKey();

byte[] encryptedData = ...

// parse CMS envelope data
CMSEnvelopedDataParser envelopedDataParser = new CMSEnvelopedDataParser(new ByteArrayInputStream(encryptedData));

// expect exactly one recipient
Collection<?> recipients = envelopedDataParser.getRecipientInfos().getRecipients();
if (recipients.size() != 1)
    throw new IllegalArgumentException();

// retrieve recipient and decode it
RecipientInformation recipient = (RecipientInformation) recipients.iterator().next();
byte[] decryptedData = recipient.getContent(privateKey, BouncyCastleProvider.PROVIDER_NAME);

Deje comentarios si me falta algo importante o si estoy cometiendo errores aquí ... ¡Gracias!

1