2009-03-10 18 views
7

Estoy creando una aplicación del lado del cliente que necesita crear un registro de la actividad del usuario, pero por diversas razones este registro no debe ser legible por el ser humano.Crear un archivo de registro cifrado

la actualidad para el desarrollo de mi Voy a crear un registro de texto sin formato que se ve algo como esto:

12/03/2009 08:34:21 -> User 'Bob' conectado 12/03/2009 08 : 34: 28 -> Navegado a la página de configuración 12/03/2009 08:34:32 -> Opción x cambiado a y

Cuando despliegue mi aplicación, el registro no debe estar en texto plano, por lo que todo el texto debe estar encriptado Esto no parece ser sencillo de lograr ya que necesito que el archivo de registro se actualice dinámicamente a medida que se agrega cada entrada. El enfoque en el que estaba pensando era crear un archivo binario, encriptar cada entrada de registro aisladamente y luego anexarlo al archivo binario con una demarcación adecuada entre cada entrada.

¿Alguien sabe de algún acercamiento común a este problema, estoy seguro de que debe haber una mejor solución!

+0

¿Tiene un entorno particular en mente? en general, usaría una base de datos y aplicaría seguridad a nivel de base de datos, no funcionaría si no usa una base de datos o si necesita poder exportar registros fácilmente. –

Respuesta

3

Esto no es lo mío, lo admitiré fácilmente, pero ¿no puedes encriptar cada entrada individualmente y luego anexarla al archivo de registro? Si usted se abstiene de cifrar la marca de tiempo, puede encontrar fácilmente las entradas que está buscando y descifrarlas cuando sea necesario.

Mi punto es que las entradas cifradas individuales que anexan a un archivo no necesariamente tienen que ser entradas binarias anexadas a un archivo binario. La encriptación con (por ejemplo) gpg producirá un ascii ilegible que se puede adjuntar a un archivo ascii. ¿Eso te resolvería el problema?

+0

¿Puede proporcionarnos un enlace a un ejemplo en Internet – shareef

3

Suponiendo que está utilizando algún tipo de marco de registro, por ejemplo, log4j y otros, entonces debería poder crear una implementación personalizada de Appender (o similar) que encripta cada entrada, como @wzzrd sugirió.

5

FWIW, la única vez que necesitaba un registrador cifrado Utilicé una clave simétrica (por motivos de rendimiento) para encriptar las entradas de registro reales.

La 'clave del archivo log simétrico' se cifró bajo una clave pública y se almacenó al principio del archivo de registro y un lector de registro independiente usó la clave privada para descifrar la 'clave del archivo de registro' y leer las entradas.

Todo esto se implementó utilizando log4j y un formato de archivo de registro XML (para que el lector lo pueda analizar más fácilmente) y cada vez que se hacían rodar los archivos de registro se generaba una nueva 'clave de archivo de registro'.

+0

Excelente solución! – erickson

+0

Excepto una cosa: la clave simétrica debe almacenarse en algún lugar en texto sin formato de todos modos, si desea cifrar/descifrar algo. Este hecho borra todos los beneficios de seguridad del cifrado asimétrico. – bytefu

+0

@bytefu: No: una clave simétrica se generó sobre la marcha y se almacenó encriptada debajo de la clave pública en el archivo de registro. – tonys

1

Me pregunto qué tipo de aplicación escribes. Un virus o un caballo de Troya? De todos modos ...

Encripte cada entrada sola, conviértala en una cadena (Base64, por ejemplo) y luego registre esa cadena como el "mensaje".

Esto le permite mantener partes del archivo legibles y solo encriptar las partes importantes.

Observe que esta moneda tiene otro lado: si crea un archivo completamente encriptado y le pide al usuario, no puede saber qué aprenderá del archivo. Por lo tanto, debe encriptar lo menos posible (contraseñas, direcciones IP, datos del cliente) para que el departamento legal pueda verificar qué información se está dejando.

Un enfoque mucho mejor sería un ofuscador para el archivo de registro. Eso simplemente reemplaza ciertos patrones con "XXX".Todavía puede ver lo que sucedió y cuando necesita un dato específico, puede solicitarlo.

[EDITAR] Esta historia tiene más implicaciones que pensarías a primera vista. Esto significa que un usuario no puede ver lo que hay en el archivo. "Usuario" no necesariamente incluye "cracker". Un cracker se concentrará en los archivos cifrados (ya que probablemente sean más importantes). Esa es la razón del viejo dicho: tan pronto como alguien tiene acceso a la máquina, no hay forma de evitar que haga algo al respecto. O para decirlo de otra manera: el hecho de que no sepas cómo no significa que otra persona tampoco lo hace. Si crees que no tienes nada que esconder, no has pensado en ti mismo.

Además, existe la responsabilidad. Supongamos que algunos datos se filtran en Internet después de obtener una copia de los registros. Dado que el usuario no tiene idea de lo que hay en los archivos de registro, ¿cómo puede demostrar en la corte que usted no fue la filtración? Los jefes pueden pedir los archivos de registro para monitorear sus peones, pidiéndoles que lo codifiquen para que los campesinos no lo noten y se quejen de él (¡o denuncien a la escoria!).

O mire desde un ángulo completamente diferente: si no hubiera un archivo de registro, nadie podría abusar de él. ¿Qué hay de habilitar la depuración solo en caso de una emergencia? Configuré log4j para mantener los últimos 200 mensajes de registro en un búfer. Si se registra un ERROR, vuelco los 200 mensajes al registro. Justificación: realmente no me importa lo que suceda durante el día. Solo me importan los errores. Con JMX, es simple establecer el nivel de depuración en ERROR y disminuirlo de forma remota en el tiempo de ejecución cuando necesite más detalles.

+0

Tristemente no tengo ni el tiempo ni la capacidad de crear un virus/troyano. El archivo de registro encriptado es realmente para proteger a alguien que accede a una máquina e inspecciona lo que ha hecho un usuario (es decir, para proteger la privacidad de los usuarios). – JamieH

+0

JamieH: Me pregunto qué respondería un virus/troyano;) Pero la pregunta sigue siendo: ¿Quién protege al usuario contra usted? O para decirlo al revés: ¿Cómo se * protegerá usted mismo cuando los usuarios lo demanden debido a la invasión de su privacidad? –

+0

Tangencialmente relacionado con el OP, pero realmente me gusta la idea de un búfer de eventos que solo se descarga a un archivo cuando hay un error. – erickson

0

Para ver .Net Bloques de aplicación de Microsoft para la funcionalidad de registro y Cifrar: http://msdn.microsoft.com/en-us/library/dd203099.aspx

que sería añadir entradas de registro cifrados a un archivo de texto plano usando la demarcación adecuada entre cada entrada para el descifrado de trabajar.

1

Encriptar cada entrada de registro individualmente disminuiría mucho la seguridad de su texto cifrado, especialmente porque está trabajando con texto plano muy predecible.

Esto es lo que puede hacer:

  1. uso de cifrado simétrico (preferiblemente AES)
  2. elegirán un maestro al azar clave
  3. elige un intervalo de seguridad (5 minutos, 10 minutos, etc.)

a continuación, elige una clave temporal al azar al comienzo de cada ventana (cada 5 minutos, cada 10 minutos, etc.)

Encripte cada elemento de registro por separado utilizando la clave temporal y anexe a un archivo de registro temporal.

Cuando la ventana está cerrada (el tiempo predeterminado está arriba), descifra cada elemento con la clave temporal, descifra el archivo de registro maestro con la clave maestra, combina los archivos y encripta con la clave maestra.

Luego, elija una nueva clave temporal y continúe.

Además, cambiar la llave maestra cada vez que gire el archivo de registro maestro (cada día, cada semana, etc.)

Esto debería proporcionar suficiente seguridad.

+0

¿podría proporcionarnos más información sobre por qué está recomendando esta solución? específicamente, ¿por qué utilizar simétrico, por qué las claves temporales aumentan la seguridad y por qué cambiar la clave maestra en cada rotación? – sazary

+0

si el rendimiento no es un problema, entonces ¿por qué emplear una llave temporal? y si lo es, descifrar todo el archivo con la clave maestra y volver a encriptarlo sigue siendo un problema, aunque al final de cada ventana. – sazary

1

No me queda claro si su preocupación es la seguridad o el implemento.

Un implemento simple es conectar con un encriptador de flujo. Un encriptador de flujo mantiene su propio estado y puede encriptar sobre la marcha.

StreamEncryptor<AES_128> encryptor; 
encryptor.connectSink(new std::ofstream("app.log")); 
encryptor.write(line); 
encryptor.write(line2); 
... 
8

no cifran entradas de registro individuales por separado y escribirlas en un archivo según lo sugerido por otros carteles, ya que un atacante podría fácilmente ser capaz de identificar patrones en el archivo de registro. Consulte el block cipher modes Wikipedia entry para obtener más información sobre este problema.

Original Encrypted using ECB mode Encrypted using other modes

En su lugar, asegúrese de que el cifrado de una entrada de registro depende de las entradas de registro anteriores. Aunque esto tiene algunos inconvenientes (no puede descifrar entradas de registro individuales ya que siempre necesita descifrar el archivo completo), hace que el cifrado sea mucho más sólido. Para nuestra propia biblioteca de registro, SmartInspect, utilizamos el cifrado AES y el modo CBC para evitar el problema de patrón. Siéntase libre de probar SmartInspect si una solución comercial sería adecuada.

+0

Aunque me gusta la idea de encriptar la entrada de registro B con información de la entrada de registro A, no estoy seguro de si realmente es apropiado conectar tu propio producto ... (Si se considera apropiado, seré el primero en humildemente eliminar este comentario, por supuesto ;-)) – wzzrd

+3

No estoy seguro wzzrd. Si estuviera buscando una solución a un problema y hay una herramienta que ya hace lo que estoy buscando, me gustaría que alguien lo señalara (incluso si es comercial). Además, espero que mi respuesta sea útil incluso si no está interesado en nuestra herramienta. –

+0

Leer un archivo de registro enorme desde el principio para encontrar una marca de tiempo determinada no es eficiente. Si se trata de un caso de uso, hay mejores modos de cifrado para usar. – erickson

0

Tengo exactamente la misma necesidad que usted. Un tipo llamado 'maybeWeCouldStealAVa' escribió una buena implementación en: How to append to AES encrypted file, sin embargo, esto no se podía desechar: tendrías que cerrar y volver a abrir el archivo cada vez que tiras un mensaje, para asegurarte de no perder nada.

por lo que he escrito mi propia clase para hacer esto:

import javax.crypto.*; 
import javax.crypto.spec.IvParameterSpec; 
import javax.crypto.spec.SecretKeySpec; 
import java.io.*; 
import java.security.*; 


public class FlushableCipherOutputStream extends OutputStream 
{ 
    private static int HEADER_LENGTH = 16; 


    private SecretKeySpec key; 
    private RandomAccessFile seekableFile; 
    private boolean flushGoesStraightToDisk; 
    private Cipher cipher; 
    private boolean needToRestoreCipherState; 

    /** the buffer holding one byte of incoming data */ 
    private byte[] ibuffer = new byte[1]; 

    /** the buffer holding data ready to be written out */ 
    private byte[] obuffer; 



    /** Each time you call 'flush()', the data will be written to the operating system level, immediately available 
    * for other processes to read. However this is not the same as writing to disk, which might save you some 
    * data if there's a sudden loss of power to the computer. To protect against that, set 'flushGoesStraightToDisk=true'. 
    * Most people set that to 'false'. */ 
    public FlushableCipherOutputStream(String fnm, SecretKeySpec _key, boolean append, boolean _flushGoesStraightToDisk) 
      throws IOException 
    { 
     this(new File(fnm), _key, append,_flushGoesStraightToDisk); 
    } 

    public FlushableCipherOutputStream(File file, SecretKeySpec _key, boolean append, boolean _flushGoesStraightToDisk) 
      throws IOException 
    { 
     super(); 

     if (! append) 
      file.delete(); 
     seekableFile = new RandomAccessFile(file,"rw"); 
     flushGoesStraightToDisk = _flushGoesStraightToDisk; 
     key = _key; 

     try { 
      cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); 

      byte[] iv = new byte[16]; 
      byte[] headerBytes = new byte[HEADER_LENGTH]; 
      long fileLen = seekableFile.length(); 
      if (fileLen % 16L != 0L) { 
       throw new IllegalArgumentException("Invalid file length (not a multiple of block size)"); 
      } else if (fileLen == 0L) { 
       // new file 

       // You can write a 16 byte file header here, including some file format number to represent the 
       // encryption format, in case you need to change the key or algorithm. E.g. "100" = v1.0.0 
       headerBytes[0] = 100; 
       seekableFile.write(headerBytes); 

       // Now appending the first IV 
       SecureRandom sr = new SecureRandom(); 
       sr.nextBytes(iv); 
       seekableFile.write(iv); 
       cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv)); 
      } else if (fileLen <= 16 + HEADER_LENGTH) { 
       throw new IllegalArgumentException("Invalid file length (need 2 blocks for iv and data)"); 
      } else { 
       // file length is at least 2 blocks 
       needToRestoreCipherState = true; 
      } 
     } catch (InvalidKeyException e) { 
      throw new IOException(e.getMessage()); 
     } catch (NoSuchAlgorithmException e) { 
      throw new IOException(e.getMessage()); 
     } catch (NoSuchPaddingException e) { 
      throw new IOException(e.getMessage()); 
     } catch (InvalidAlgorithmParameterException e) { 
      throw new IOException(e.getMessage()); 
     } 
    } 


    /** 
    * Writes one _byte_ to this output stream. 
    */ 
    public void write(int b) throws IOException { 
     if (needToRestoreCipherState) 
      restoreStateOfCipher(); 
     ibuffer[0] = (byte) b; 
     obuffer = cipher.update(ibuffer, 0, 1); 
     if (obuffer != null) { 
      seekableFile.write(obuffer); 
      obuffer = null; 
     } 
    } 

    /** Writes a byte array to this output stream. */ 
    public void write(byte data[]) throws IOException { 
     write(data, 0, data.length); 
    } 

    /** 
    * Writes <code>len</code> bytes from the specified byte array 
    * starting at offset <code>off</code> to this output stream. 
    * 
    * @param  data  the data. 
    * @param  off the start offset in the data. 
    * @param  len the number of bytes to write. 
    */ 
    public void write(byte data[], int off, int len) throws IOException 
    { 
     if (needToRestoreCipherState) 
      restoreStateOfCipher(); 
     obuffer = cipher.update(data, off, len); 
     if (obuffer != null) { 
      seekableFile.write(obuffer); 
      obuffer = null; 
     } 
    } 


    /** The tricky stuff happens here. We finalise the cipher, write it out, but then rewind the 
    * stream so that we can add more bytes without padding. */ 
    public void flush() throws IOException 
    { 
     try { 
      if (needToRestoreCipherState) 
       return; // It must have already been flushed. 
      byte[] obuffer = cipher.doFinal(); 
      if (obuffer != null) { 
       seekableFile.write(obuffer); 
       if (flushGoesStraightToDisk) 
        seekableFile.getFD().sync(); 
       needToRestoreCipherState = true; 
      } 
     } catch (IllegalBlockSizeException e) { 
      throw new IOException("Illegal block"); 
     } catch (BadPaddingException e) { 
      throw new IOException("Bad padding"); 
     } 
    } 

    private void restoreStateOfCipher() throws IOException 
    { 
     try { 
      // I wish there was a more direct way to snapshot a Cipher object, but it seems there's not. 
      needToRestoreCipherState = false; 
      byte[] iv = cipher.getIV(); // To help avoid garbage, re-use the old one if present. 
      if (iv == null) 
       iv = new byte[16]; 
      seekableFile.seek(seekableFile.length() - 32); 
      seekableFile.read(iv); 
      byte[] lastBlockEnc = new byte[16]; 
      seekableFile.read(lastBlockEnc); 
      cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv)); 
      byte[] lastBlock = cipher.doFinal(lastBlockEnc); 
      seekableFile.seek(seekableFile.length() - 16); 
      cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv)); 
      byte[] out = cipher.update(lastBlock); 
      assert out == null || out.length == 0; 
     } catch (Exception e) { 
      throw new IOException("Unable to restore cipher state"); 
     } 
    } 

    public void close() throws IOException 
    { 
     flush(); 
     seekableFile.close(); 
    } 
} 

He aquí un ejemplo de su uso:

import org.junit.Test; 
import javax.crypto.Cipher; 
import javax.crypto.CipherInputStream; 
import javax.crypto.spec.IvParameterSpec; 
import javax.crypto.spec.SecretKeySpec; 
import java.io.*; 
import java.io.BufferedWriter; 



public class TestFlushableCipher { 
    private static byte[] keyBytes = new byte[] { 
      // Change these numbers, lest other StackOverflow readers can decrypt your files. 
      -53, 93, 59, 108, -34, 17, -72, -33, 126, 93, -62, -50, 106, -44, 17, 55 
    }; 
    private static SecretKeySpec key = new SecretKeySpec(keyBytes,"AES"); 
    private static int HEADER_LENGTH = 16; 


    private static BufferedWriter flushableEncryptedBufferedWriter(File file, boolean append) throws Exception 
    { 
     FlushableCipherOutputStream fcos = new FlushableCipherOutputStream(file, key, append, false); 
     return new BufferedWriter(new OutputStreamWriter(fcos, "UTF-8")); 
    } 

    private static InputStream readerEncryptedByteStream(File file) throws Exception 
    { 
     FileInputStream fin = new FileInputStream(file); 
     byte[] iv = new byte[16]; 
     byte[] headerBytes = new byte[HEADER_LENGTH]; 
     if (fin.read(headerBytes) < HEADER_LENGTH) 
      throw new IllegalArgumentException("Invalid file length (failed to read file header)"); 
     if (headerBytes[0] != 100) 
      throw new IllegalArgumentException("The file header does not conform to our encrypted format."); 
     if (fin.read(iv) < 16) { 
      throw new IllegalArgumentException("Invalid file length (needs a full block for iv)"); 
     } 
     Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); 
     cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv)); 
     return new CipherInputStream(fin,cipher); 
    } 

    private static BufferedReader readerEncrypted(File file) throws Exception 
    { 
     InputStream cis = readerEncryptedByteStream(file); 
     return new BufferedReader(new InputStreamReader(cis)); 
    } 

    @Test 
    public void test() throws Exception { 
     File zfilename = new File("c:\\WebEdvalData\\log.x"); 

     BufferedWriter cos = flushableEncryptedBufferedWriter(zfilename, false); 
     cos.append("Sunny "); 
     cos.append("and green. \n"); 
     cos.close(); 

     int spaces=0; 
     for (int i = 0; i<10; i++) { 
      cos = flushableEncryptedBufferedWriter(zfilename, true); 
      for (int j=0; j < 2; j++) { 
       cos.append("Karelia and Tapiola" + i); 
       for (int k=0; k < spaces; k++) 
        cos.append(" "); 
       spaces++; 
       cos.append("and other nice things. \n"); 
       cos.flush(); 
       tail(zfilename); 
      } 
      cos.close(); 
     } 

     BufferedReader cis = readerEncrypted(zfilename); 
     String msg; 
     while ((msg=cis.readLine()) != null) { 
      System.out.println(msg); 
     } 
     cis.close(); 
    } 

    private void tail(File filename) throws Exception 
    { 
     BufferedReader infile = readerEncrypted(filename); 
     String last = null, secondLast = null; 
     do { 
      String msg = infile.readLine(); 
      if (msg == null) 
       break; 
      if (! msg.startsWith("}")) { 
       secondLast = last; 
       last = msg; 
      } 
     } while (true); 
     if (secondLast != null) 
      System.out.println(secondLast); 
     System.out.println(last); 
     System.out.println(); 
    } 
} 
1

pregunta muy viejo y estoy seguro de que el mundo de la tecnología ha hecho mucho progreso, pero FWIW Bruce Schneier y John Kelsey escribieron un documento sobre cómo hacer esto: https://www.schneier.com/paper-auditlogs.html

El contexto no es solo la seguridad sino también la prevención de la corrupción o el cambio de e xisting datos de archivo de registro si el sistema que aloja los archivos de registro/auditoría está en peligro.

Cuestiones relacionadas