diff --git a/.gitignore b/.gitignore index dcb08b4..a2cd4cb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ + + +*cover.out /ecrypto # Compiled Object files, Static and Dynamic libs (Shared Objects) *.o diff --git a/doc/fileformat.md b/doc/fileformat.md new file mode 100644 index 0000000..fa782c4 --- /dev/null +++ b/doc/fileformat.md @@ -0,0 +1,25 @@ + +### public key +Note that any private key are a public key. + +~~~ + +~~~ + +### private key + + + + +### cypher format + +We use the `.cyp` file extension for the encrypted file. A .cyp is nothing +else than a zip file containing the following shit. + +1. a `data.encrypted` containing the encrypted data +2. a `meta_data.yml` file containing the meta data of the encrypted file + * symetric encryption algorithm + * mode + * encrypted symetric key + * hash of the encrypted data (always use full for validation) +3. a 'signature' file with the signature of the `meta_data.yml` file \ No newline at end of file diff --git a/encrypter/encrypter.go b/encrypter/encrypter.go index 49ed04b..b44f2b7 100644 --- a/encrypter/encrypter.go +++ b/encrypter/encrypter.go @@ -1,16 +1,77 @@ package encrypter import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" "github.com/didiercrunch/ecrypto/keys" + "io" ) -type Encrypter struct { - PublicKey keys.PublicKey - PrivateKey keys.PrivateKey - InputPath string - OutputPath string +type FileEncrypter struct { + PublicKey keys.PublicKey + PrivateKey keys.PrivateKey + InputPath string + OutputPath string + randomReader io.Reader + + iv []byte + key []byte } -func (this *Encrypter) Encrypt() error { - // encrypt file +func NewEncrypter() *FileEncrypter { + ret := new(FileEncrypter) + ret.randomReader = rand.Reader + return ret +} + +func (this *FileEncrypter) createSymetricKey(size int) ([]byte, error) { + key := make([]byte, size) + _, err := io.ReadFull(this.randomReader, key) + return key, err +} + +func (this *FileEncrypter) getCypherBlock() (cipher.Block, error) { + var err error + this.key, err = this.createSymetricKey(32) + if err != nil { + return nil, err + } + return aes.NewCipher(this.key) +} + +func (this *FileEncrypter) createIV(block cipher.Block) (iv []byte, err error) { + return this.createSymetricKey(block.BlockSize()) +} + +func (this *FileEncrypter) createStreamMode(block cipher.Block) (stream cipher.Stream, err error) { + this.iv, err = this.createIV(block) + if err != nil { + return nil, err + } + return cipher.NewOFB(block, this.iv), err +} + +func (this *FileEncrypter) createFileHandlerAndCypherBlock() (stream cipher.Stream, err error) { + cypherBlock, err := this.getCypherBlock() + if err != nil { + return nil, err + } + return this.createStreamMode(cypherBlock) +} + +func (this *FileEncrypter) encrypeReaderToWriter(clearInput io.Reader, encryptedOutput io.Writer) error { + stream, err := this.createFileHandlerAndCypherBlock() + if err != nil { + return err + } + writer := &cipher.StreamWriter{S: stream, W: encryptedOutput} + if _, err := io.Copy(writer, clearInput); err != nil { + return err + } + return nil +} + +func (this *FileEncrypter) Encrypt() error { + return nil } diff --git a/encrypter/encrypter_test.go b/encrypter/encrypter_test.go new file mode 100644 index 0000000..a251f4b --- /dev/null +++ b/encrypter/encrypter_test.go @@ -0,0 +1,204 @@ +package encrypter + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "fmt" + "reflect" + "strings" + "testing" +) + +var _ = fmt.Print + +type mockIoWriter struct { + state []byte +} + +func (this *mockIoWriter) Write(p []byte) (n int, err error) { + this.state = append(this.state, p...) + return len(p), nil +} + +func (this *mockIoWriter) String() string { + return string(this.state) +} + +func newMockIoWriter() *mockIoWriter { + return &mockIoWriter{make([]byte, 0, 1024)} +} + +func (this *mockIoWriter) Length() int { + return len(this.state) +} + +func (this *mockIoWriter) Bytes() []byte { + return this.state +} + +func range_(length int) []byte { + ret := make([]byte, length) + for i, _ := range ret { + ret[i] = byte(i) + } + return ret +} +func fromRange(from, to int) []byte { + ret := make([]byte, to-from) + for i, _ := range ret { + ret[i] = byte(i + from) + } + return ret +} + +func createMockRandomData(length int) []byte { + ret := make([]byte, length) + for i, _ := range ret { + ret[i] = byte(i) + } + return ret +} + +func NewMockEncrypter() *FileEncrypter { + ret := new(FileEncrypter) + mockRandomData := createMockRandomData(1024) + ret.randomReader = bytes.NewReader(mockRandomData) + return ret +} + +func TestCreateSymetricKey(t *testing.T) { + c := NewMockEncrypter() + key, e := c.createSymetricKey(512) + if e != nil { + t.Error(e) + } else if len(key) != 512 { + t.Error("bad key lenght") + } else if !reflect.DeepEqual(key, createMockRandomData(512)) { + t.Fail() + } +} + +func TestGetCypherBlock(t *testing.T) { + c := NewMockEncrypter() + if c.key != nil { + t.Error("expected nil key at this point") + } + block, err := c.getCypherBlock() + if err != nil { + t.Error(err) + } + if block.BlockSize() != 16 { + t.Fail() + } + src := createMockRandomData(16) + dst := make([]byte, 16) + expected := []byte{90, 110, 4, 87, 8, 251, 113, 150, 240, 46, 85, 61, 2, 195, 166, 146} + + block.Encrypt(dst, src) + if !reflect.DeepEqual(dst, expected) { + t.Error("bad aes encryption") + } + if !reflect.DeepEqual(c.key, range_(32)) { + t.Error("bad key", c.key) + } +} + +func TestNewEncrypter(t *testing.T) { + c := NewEncrypter() + if c.randomReader != rand.Reader { + t.Fail() + } +} + +func TestCreateIV(t *testing.T) { + m := NewMockEncrypter() + b, err := aes.NewCipher([]byte("example key 1234")) + if err != nil { + t.Error(err) + return + } + iv, err := m.createIV(b) + if err != nil { + t.Error(err) + return + } + expt := range_(16) + if !reflect.DeepEqual(iv, expt) { + t.Error("bad iv", iv) + } +} + +func TestCreateStreamMode(t *testing.T) { + m := NewMockEncrypter() + if m.iv != nil { + t.Error("expected nil iv at first") + } + b, err := aes.NewCipher([]byte("example key 1234")) + if err != nil { + t.Error(err) + return + } + _, err = m.createStreamMode(b) + if err != nil { + t.Error(err) + return + } + exptIv := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} + if !reflect.DeepEqual(m.iv, exptIv) { + t.Error("bad iv", m.iv) + } +} + +func TestCreateFileHandlerAndCypherBlock(t *testing.T) { + m := NewMockEncrypter() + if m.iv != nil { + t.Error("expected nil iv at first") + } + + _, err := m.createFileHandlerAndCypherBlock() + if err != nil { + t.Error(err) + } + if !reflect.DeepEqual(m.key, range_(32)) { + t.Error("bad key", m.key) + } + if !reflect.DeepEqual(m.iv, fromRange(32, 32+16)) { + t.Error("bad iv", m.iv) + } +} + +func TestSimpleEncrypeReaderToWriter(t *testing.T) { + reader := strings.NewReader("some plain text") + writer := newMockIoWriter() + m := NewMockEncrypter() + if err := m.encrypeReaderToWriter(reader, writer); err != nil { + t.Error(err) + } +} + +func decryptEncryptedTextWithAESAlgorithmAndOFBMode(key, iv, encryptedText []byte) (string, error) { + block, err := aes.NewCipher(key) + if err != nil { + return "", err + } + stream := cipher.NewOFB(block, iv) + ret := make([]byte, len(encryptedText)) + stream.XORKeyStream(ret, encryptedText) + return string(ret), nil +} + +func TestEncryptDecrypt(t *testing.T) { + reader := strings.NewReader("some plain text") + writer := newMockIoWriter() + m := NewMockEncrypter() + if err := m.encrypeReaderToWriter(reader, writer); err != nil { + t.Error(err) + } + if plaintext2, err := decryptEncryptedTextWithAESAlgorithmAndOFBMode(m.key, m.iv, writer.Bytes()); err != nil { + t.Error(err) + } else if string(plaintext2) != "some plain text" { + t.Error("bad encryption/decryption process") + } +} diff --git a/keygenerator/key_generator.go b/keygenerator/key_generator.go index 12fd14f..23ad1ba 100644 --- a/keygenerator/key_generator.go +++ b/keygenerator/key_generator.go @@ -26,7 +26,6 @@ func (this *KeyGenerator) ensureDirectoryExists(dir string) error { return errors.New(dir + " is not a directory") } return nil - } func (this *KeyGenerator) saveKeyAsJSON(key interface{}, filepath string) error {