2009-03-10 5 views
7

사용자 작업의 로그를 만들어야하는 클라이언트 측 응용 프로그램을 만들고 있지만 여러 가지 이유로이 로그를 사람이 읽을 수 없어야합니다.암호화 된 로그 파일 만들기

현재 내 개발을 위해 내가 이렇게 보이는 일반 텍스트 로그를 만드는 오전 :

2009년 12월 3일 8시 34분 21초 -> 사용자 '밥' 2009년 12월 3일 08 로그인 : 34 : 28 -> 구성 페이지로 이동 12/03/2009 08:34:32 -> 옵션 x가 y로 변경되었습니다.

응용 프로그램을 배포 할 때 로그는 일반 텍스트가 아니어야하므로 모든 텍스트가 암호화. 각 항목이 추가 될 때 로그 파일을 동적으로 업데이트해야하므로이 작업은 간단하지 않습니다. 내가 생각한 접근 방식은 바이너리 파일을 생성하고, 각 로그 엔트리를 개별적으로 암호화 한 다음, 각 엔트리 사이에 적절한 경계를 설정하여 바이너리 파일에 추가하는 것이었다.

누구나이 문제에 대한 일반적인 접근법을 알고 있습니까? 더 나은 해결책이 있어야합니다.

+0

염두에두고 특별한 환경이 있습니까? 일반적으로 데이터베이스를 사용하고 데이터베이스 수준에서 보안을 적용하며 데이터베이스를 사용하지 않거나 로그를 쉽게 내보낼 수 있어야하는 경우 작동하지 않습니다. –

답변

3

이것은 실제로 내 것이 아니지만 쉽게 인정할 수 있지만 각 항목을 개별적으로 암호화 한 다음 로그 파일에 추가 할 수는 없습니까? 타임 스탬프 암호화를 자제하면 찾고있는 항목을 쉽게 찾고 필요한 경우 암호를 해독 할 수 있습니다.

주로 개인 암호화 된 항목을 파일에 추가한다고해서 반드시 이진 파일에 이진 항목이 추가 될 필요는 없습니다. (예를 들어) gpg로 암호화하면 ascii 파일에 추가 할 수있는 ascii garble이 생깁니다. 그게 당신 문제를 해결할 수 있을까요?

+0

인터넷에있는 예제에 대한 링크를 제공 할 수 있습니까 – shareef

3

@wzzrd이 제안은, 예를 들어,이 외 log4j, 다음 각 항목을 암호화 Appender (또는 유사)의 사용자 지정 구현을 만들 수 있어야합니다, 로깅 프레임 워크의 일종을 사용하는 가정.

5

FWIW, 필자는 성능상의 이유로 대칭 키를 사용하여 실제 로그 항목을 암호화하는 암호화 된 로거가 필요했습니다.

공개 키를 사용하여 대칭 '로그 파일 키'를 암호화 한 다음 로그 파일의 시작 부분에 저장하고 별도의 로그 판독기가 개인 키를 사용하여 '로그 파일 키'를 해독하고 항목을 읽습니다.

log4j 및 XML 로그 파일 형식을 사용하여 모든 것을 구현했으며 (독자가 쉽게 파싱 할 수 있도록) 로그 파일을 롤오버 할 때마다 새로운 '로그 파일 키'가 생성되었습니다.

+0

우수 솔루션! – erickson

+0

한 가지를 제외하고는 : 뭔가 암호화/해독하려면 대칭 키를 일반 텍스트 어딘가에 저장해야합니다. 이 사실은 비대칭 암호화로 인한 모든 보안 이점을 없앴습니다. – bytefu

+0

@bytefu : Nope - 대칭 키가 즉시 생성되고 공개 키로 암호화되어 로그 파일에 저장됩니다. – tonys

1

어떤 종류의 응용 프로그램을 작성하는지 궁금합니다. 바이러스 또는 트로이 목마? 어쨌든 ...

각 항목을 혼자서 암호화하고 일부 문자열 (예 : Base64)으로 변환 한 다음 해당 문자열을 "메시지"로 기록하십시오.

이렇게하면 파일의 일부분을 읽을 수있게 유지하고 중요한 부분 만 암호화 할 수 있습니다.

이 동전에는 다른면이 있습니다. 완전히 암호화 된 파일을 만들고 사용자에게 물어 보면 파일에서 무엇을 배울 것인지 알 수 없습니다. 따라서 법무 부서에서 어떤 데이터가 남았는지 확인할 수 있도록 가능한 한 적은 수의 데이터 (암호, IP 주소, 고객 데이터)를 암호화해야합니다.

로그 파일의 obfuscator가 훨씬 더 나은 방법입니다. 이는 단순히 특정 패턴을 "XXX"로 대체합니다.무슨 일이 있었는지 여전히 볼 수 있으며 특정 데이터가 필요할 때 요청할 수 있습니다.

[편집]이 이야기는 언뜻 생각하면 더 많은 영향을줍니다. 이는 실제로 사용자가 파일의 내용을 볼 수 없다는 것을 의미합니다. "사용자"는 반드시 "크래커"를 포함하지 않습니다. 크래커는 암호화 된 파일에 집중합니다 (아마 더 중요하기 때문에). 그것이 오래된 말의 이유입니다. 누군가가 기계에 접근하자 마자 그 사람이 그 기계에서 어떤 일도하지 못하게 할 방법이 없습니다. 또는 다른 방법으로 말하기 : 당신이 어떻게 다른 사람도 그렇지 않다는 것을 의미하지 않는다는 것을 모르기 때문에. 당신이 숨길 것이 없다고 생각한다면, 당신은 자신에 대해 생각하지 않았을 것입니다.

또한 책임 문제가 있습니다. 로그 사본을 얻은 후 인터넷에서 일부 데이터가 유출됩니다. 사용자는 로그 파일에 무엇이 있는지 전혀 모르기 때문에 법원에서 누설이 아니라는 것을 어떻게 증명할 수 있습니까? 상사는 로그 파일을보고 폰을 모니터하고, 인코딩하여 소작농이이를 알아 채지 못하게 할 수도 있습니다 (또는 쓰레기를 고소하십시오!).

완전히 다른 각도에서 보겠습니다. 로그 파일이 없으면 아무도 파일을 남용 할 수 없습니다. 비상시에만 디버깅을 활성화하는 방법은 무엇입니까? 나는 지난 200 로그 메시지를 버퍼에 보관하도록 log4j를 구성했다. 오류가 기록되면 200 개의 메시지를 로그에 덤프합니다. 이론적 설명 : 나는 하루 동안 무슨 일이 일어나 든 상관하지 않는다. 나는 버그 만 신경 쓴다. JMX를 사용하면 디버그 레벨을 ERROR로 설정하는 것이 간단합니다. 자세한 내용이 필요할 때 런타임에 원격으로 낮추는 것이 간단합니다.

+0

슬프게도 나는 바이러스/트로이를 만들 시간도 능력도 없다. 암호화 된 로그 파일은 실제로 컴퓨터에 액세스하여 사용자가 수행 한 작업 (즉, 사용자의 개인 정보를 보호하기 위해)을 검사하는 사용자를 보호합니다. – JamieH

+0

JamieH : 바이러스/트로이 작가가 무엇을 대답하는지 궁금합니다.) 그러나 질문은 남아 있습니다. 누가 사용자를 당신으로부터 보호합니까? 또는 다른 방법으로 말하기 : 사용자가 사생활 침해로 사용자를 고소 할 때 * 자신을 어떻게 보호합니까? –

+0

OP와 접하게 관련되어 있지만 오류가있을 때만 파일로 플러시되는 이벤트 버퍼에 대한 아이디어를 정말 좋아합니다. – erickson

0

닷넷 로그 및 암호화 기능을 위해 Microsoft 응용 프로그램 블록을 참조하십시오 : http://msdn.microsoft.com/en-us/library/dd203099.aspx

나는 암호 해독이 작동하려면 각 항목 사이에 적절한 경계를 사용하여 일반 텍스트 파일에 암호화 된 로그 항목을 추가 할 것입니다.

1

각 로그 항목을 개별적으로 암호화하면 암호 텍스트의 보안이 크게 저하됩니다. 특히 예측할 수있는 일반 텍스트로 작업하고 있기 때문에 더욱 그렇습니다.

  1. 를 사용하여 대칭 암호화 (바람직하게는 AES)
  2. 이 (5 분, 10 분 등)
  3. 을 보안 창을 선택 키 임의의 마스터를 선택을 : 여기

    당신이 무엇을 할 수

그런 다음 각 창 시작 부분에 임의의 임시 키를 선택하십시오 (매 5 분마다, 10 분마다 등)

임시 키를 사용하여 각 로그 항목을 별도로 암호화하고 임시 로그 파일에 추가하십시오.

임시 키를 사용하여 각 요소의 암호를 해독하고 마스터 키를 사용하여 마스터 로그 파일의 암호를 해독 한 다음 파일을 병합하고 마스터 키를 사용하여 암호화합니다.

그런 다음 새 임시 키를 선택하고 계속하십시오.

또한, 마스터 키 당신이 마스터 로그 파일 (매일, 매주, 등)

이 충분한 보안을 제공한다 회전 각 시간을 변경합니다.

+0

이 솔루션을 추천하는 이유에 대한 정보를 더 제공 할 수 있습니까? 특히 대칭을 사용하는 이유, 임시 키가 보안을 강화하는 이유 및 각 순환에서 마스터 키를 변경하는 이유는 무엇입니까? – sazary

+0

성능에 문제가 없다면 임시 키를 사용해야하는 이유는 무엇입니까? 그렇다면 마스터 키로 전체 파일을 해독하고 다시 암호화하는 것은 각 창 끝에서 여전히 문제입니다. – sazary

1

보안 또는 구현에 대한 걱정이 커지지는 않습니다.

간단한 구현은 스트림 암호화자를 연결하는 것입니다. 스트림 암호화 기는 자체 상태를 유지하며 즉시 암호화 할 수 있습니다.

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

공격자가 로그 파일의 패턴을 쉽게 식별 할 수 있기 때문에 개별 로그 항목을 별도로 암호화하지 않고 다른 포스터에서 제안한대로 파일에 기록하지 마십시오. 이 문제에 대해 자세히 알아 보려면 block cipher modes Wikipedia entry을 참조하십시오. Encrypted using ECB mode Encrypted using other modes

Original

대신, 로그 항목의 암호화가 이전 로그 항목에 따라 달라집니다 있는지 확인하십시오. 이 방법에는 몇 가지 단점이 있지만 전체 파일의 암호를 해독해야하므로 개별 로그 항목의 암호를 해독 할 수 없으므로 암호화가 훨씬 강력 해집니다. 자체 로깅 라이브러리 인 SmartInspect의 경우 패턴 문제를 피하기 위해 AES 암호화와 CBC 모드를 사용합니다. 상용 솔루션이 적합한 경우 SmartInspect을 시도해보십시오.

+0

로그 항목 A의 정보로 로그 항목 B를 암호화하는 아이디어가 마음에 들지만, 여기에 자신의 제품을 연결하는 것이 실제로 적절한 것인지 확신 할 수 없습니다 ... (적절한 것으로 간주되면 나는 겸손하게 첫 번째가 될 것입니다. 물론이 댓글을 삭제하십시오 ;-)) – wzzrd

+3

wzzrd가 확실하지 않습니다. 문제에 대한 해결책을 찾고 이미 내가 원하는 것을 수행 할 수있는 도구가 있다면 누군가 (상업적이라고하더라도) 지적하면 기쁠 것입니다. 게다가 우리 툴에 관심이 없다고해도 내 대답이 유용하길 바랍니다. –

+0

처음부터 거대한 로그 파일을 읽으면 특정 시간 소인을 찾는 것이 효율적이지 않습니다. 이것이 유스 케이스 인 경우 사용할 더 나은 암호 모드가 있습니다. – erickson

0

나는 당신과 똑같은 필요를 가지고 있습니다. 'maybeWeCouldStealAVa'라고 불리는 어떤 사람은 How to append to AES encrypted file에 좋은 구현을 작성했으나 플러시가 가능하지 않아 고통을 받았습니다. 메시지를 플러시 할 때마다 파일을 닫고 다시 열어야합니다. 여기

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(); 
    } 
} 

이 그것을 사용의 예입니다 :

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

아주 오래된 질문 나는 기술 세계가 만든 확신

그래서 나는이 작업을 수행하기 위해 내 자신의 클래스를 작성했습니다 많은 진전을 보였으 나 FWIW Bruce Schneier와 John Kelsey는이 작업을 수행하는 방법에 대한 논문을 썼습니다. https://www.schneier.com/paper-auditlogs.html

컨텍스트는 보안뿐만 아니라 전자의 손상이나 변경을 방지합니다. 로그/감사 파일을 호스팅하는 시스템이 손상되면 로그 파일 데이터가 xisting됩니다.