summaryrefslogtreecommitdiffstats
path: root/encryption.go
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 17:13:42 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 17:13:42 +0000
commit119fcc3f6b480eb6791dfb79b6f9eae641705db0 (patch)
tree103ae60fcd710bfa9b07791630a4230ba4a6396d /encryption.go
parentInitial commit. (diff)
downloadgolang-github-containers-luksy-119fcc3f6b480eb6791dfb79b6f9eae641705db0.tar.xz
golang-github-containers-luksy-119fcc3f6b480eb6791dfb79b6f9eae641705db0.zip
Adding upstream version 0.0~git20231017.6a3592c+ds1.upstream/0.0_git20231017.6a3592c+ds1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'encryption.go')
-rw-r--r--encryption.go572
1 files changed, 572 insertions, 0 deletions
diff --git a/encryption.go b/encryption.go
new file mode 100644
index 0000000..242bceb
--- /dev/null
+++ b/encryption.go
@@ -0,0 +1,572 @@
+package luksy
+
+import (
+ "crypto/aes"
+ "crypto/cipher"
+ "crypto/rand"
+ "crypto/sha1"
+ "crypto/sha256"
+ "crypto/sha512"
+ "encoding/binary"
+ "errors"
+ "fmt"
+ "hash"
+ "io"
+ "strings"
+
+ "github.com/aead/serpent"
+ "golang.org/x/crypto/cast5"
+ "golang.org/x/crypto/ripemd160"
+ "golang.org/x/crypto/twofish"
+ "golang.org/x/crypto/xts"
+)
+
+func v1encrypt(cipherName, cipherMode string, ivTweak int, key []byte, plaintext []byte, sectorSize int, bulk bool) ([]byte, error) {
+ var err error
+ var newBlockCipher func([]byte) (cipher.Block, error)
+ ciphertext := make([]byte, len(plaintext))
+
+ switch cipherName {
+ case "aes":
+ newBlockCipher = aes.NewCipher
+ case "twofish":
+ newBlockCipher = func(key []byte) (cipher.Block, error) { return twofish.NewCipher(key) }
+ case "cast5":
+ newBlockCipher = func(key []byte) (cipher.Block, error) { return cast5.NewCipher(key) }
+ case "serpent":
+ newBlockCipher = serpent.NewCipher
+ default:
+ return nil, fmt.Errorf("unsupported cipher %s", cipherName)
+ }
+ if sectorSize == 0 {
+ sectorSize = V1SectorSize
+ }
+ switch sectorSize {
+ default:
+ return nil, fmt.Errorf("invalid sector size %d", sectorSize)
+ case 512, 1024, 2048, 4096:
+ }
+
+ switch cipherMode {
+ case "ecb":
+ cipher, err := newBlockCipher(key)
+ if err != nil {
+ return nil, fmt.Errorf("initializing encryption: %w", err)
+ }
+ for processed := 0; processed < len(plaintext); processed += cipher.BlockSize() {
+ blockLeft := sectorSize
+ if processed+blockLeft > len(plaintext) {
+ blockLeft = len(plaintext) - processed
+ }
+ cipher.Encrypt(ciphertext[processed:processed+blockLeft], plaintext[processed:processed+blockLeft])
+ }
+ case "cbc-plain":
+ block, err := newBlockCipher(key)
+ if err != nil {
+ return nil, fmt.Errorf("initializing encryption: %w", err)
+ }
+ for processed := 0; processed < len(plaintext); processed += sectorSize {
+ blockLeft := sectorSize
+ if processed+blockLeft > len(plaintext) {
+ blockLeft = len(plaintext) - processed
+ }
+ ivValue := processed/sectorSize + ivTweak
+ if bulk { // iv_large_sectors is not being used
+ ivValue *= sectorSize / V1SectorSize
+ }
+ iv0 := make([]byte, block.BlockSize())
+ binary.LittleEndian.PutUint32(iv0, uint32(ivValue))
+ cipher := cipher.NewCBCEncrypter(block, iv0)
+ cipher.CryptBlocks(ciphertext[processed:processed+blockLeft], plaintext[processed:processed+blockLeft])
+ }
+ case "cbc-plain64":
+ block, err := newBlockCipher(key)
+ if err != nil {
+ return nil, fmt.Errorf("initializing encryption: %w", err)
+ }
+ for processed := 0; processed < len(plaintext); processed += sectorSize {
+ blockLeft := sectorSize
+ if processed+blockLeft > len(plaintext) {
+ blockLeft = len(plaintext) - processed
+ }
+ ivValue := processed/sectorSize + ivTweak
+ if bulk { // iv_large_sectors is not being used
+ ivValue *= sectorSize / V1SectorSize
+ }
+ iv0 := make([]byte, block.BlockSize())
+ binary.LittleEndian.PutUint64(iv0, uint64(ivValue))
+ cipher := cipher.NewCBCEncrypter(block, iv0)
+ cipher.CryptBlocks(ciphertext[processed:processed+blockLeft], plaintext[processed:processed+blockLeft])
+ }
+ case "cbc-essiv:sha256":
+ hasherName := strings.TrimPrefix(cipherMode, "cbc-essiv:")
+ hasher, err := hasherByName(hasherName)
+ if err != nil {
+ return nil, fmt.Errorf("initializing encryption using hash %s: %w", hasherName, err)
+ }
+ h := hasher()
+ h.Write(key)
+ makeiv, err := newBlockCipher(h.Sum(nil))
+ if err != nil {
+ return nil, fmt.Errorf("initializing encryption: %w", err)
+ }
+ block, err := newBlockCipher(key)
+ if err != nil {
+ return nil, fmt.Errorf("initializing encryption: %w", err)
+ }
+ for processed := 0; processed < len(plaintext); processed += sectorSize {
+ blockLeft := sectorSize
+ if processed+blockLeft > len(plaintext) {
+ blockLeft = len(plaintext) - processed
+ }
+ ivValue := (processed/sectorSize + ivTweak)
+ if bulk { // iv_large_sectors is not being used
+ ivValue *= sectorSize / V1SectorSize
+ }
+ plain0 := make([]byte, makeiv.BlockSize())
+ binary.LittleEndian.PutUint64(plain0, uint64(ivValue))
+ iv0 := make([]byte, makeiv.BlockSize())
+ makeiv.Encrypt(iv0, plain0)
+ cipher := cipher.NewCBCEncrypter(block, iv0)
+ cipher.CryptBlocks(ciphertext[processed:processed+blockLeft], plaintext[processed:processed+blockLeft])
+ }
+ case "xts-plain":
+ cipher, err := xts.NewCipher(newBlockCipher, key)
+ if err != nil {
+ return nil, fmt.Errorf("initializing encryption: %w", err)
+ }
+ for processed := 0; processed < len(plaintext); processed += sectorSize {
+ blockLeft := sectorSize
+ if processed+blockLeft > len(plaintext) {
+ blockLeft = len(plaintext) - processed
+ }
+ sector := uint64(processed/sectorSize + ivTweak)
+ if bulk { // iv_large_sectors is not being used
+ sector *= uint64(sectorSize / V1SectorSize)
+ }
+ sector = sector % 0x100000000
+ cipher.Encrypt(ciphertext[processed:processed+blockLeft], plaintext[processed:processed+blockLeft], sector)
+ }
+ case "xts-plain64":
+ cipher, err := xts.NewCipher(newBlockCipher, key)
+ if err != nil {
+ return nil, fmt.Errorf("initializing encryption: %w", err)
+ }
+ for processed := 0; processed < len(plaintext); processed += sectorSize {
+ blockLeft := sectorSize
+ if processed+blockLeft > len(plaintext) {
+ blockLeft = len(plaintext) - processed
+ }
+ sector := uint64(processed/sectorSize + ivTweak)
+ if bulk { // iv_large_sectors is not being used
+ sector *= uint64(sectorSize / V1SectorSize)
+ }
+ cipher.Encrypt(ciphertext[processed:processed+blockLeft], plaintext[processed:processed+blockLeft], sector)
+ }
+ default:
+ return nil, fmt.Errorf("unsupported cipher mode %s", cipherMode)
+ }
+
+ if err != nil {
+ return nil, fmt.Errorf("cipher error: %w", err)
+ }
+ return ciphertext, nil
+}
+
+func v1decrypt(cipherName, cipherMode string, ivTweak int, key []byte, ciphertext []byte, sectorSize int, bulk bool) ([]byte, error) {
+ var err error
+ var newBlockCipher func([]byte) (cipher.Block, error)
+ plaintext := make([]byte, len(ciphertext))
+
+ switch cipherName {
+ case "aes":
+ newBlockCipher = aes.NewCipher
+ case "twofish":
+ newBlockCipher = func(key []byte) (cipher.Block, error) { return twofish.NewCipher(key) }
+ case "cast5":
+ newBlockCipher = func(key []byte) (cipher.Block, error) { return cast5.NewCipher(key) }
+ case "serpent":
+ newBlockCipher = serpent.NewCipher
+ default:
+ return nil, fmt.Errorf("unsupported cipher %s", cipherName)
+ }
+ if sectorSize == 0 {
+ sectorSize = V1SectorSize
+ }
+ switch sectorSize {
+ default:
+ return nil, fmt.Errorf("invalid sector size %d", sectorSize)
+ case 512, 1024, 2048, 4096:
+ }
+
+ switch cipherMode {
+ case "ecb":
+ cipher, err := newBlockCipher(key)
+ if err != nil {
+ return nil, fmt.Errorf("initializing decryption: %w", err)
+ }
+ for processed := 0; processed < len(ciphertext); processed += cipher.BlockSize() {
+ blockLeft := sectorSize
+ if processed+blockLeft > len(ciphertext) {
+ blockLeft = len(ciphertext) - processed
+ }
+ cipher.Decrypt(plaintext[processed:processed+blockLeft], ciphertext[processed:processed+blockLeft])
+ }
+ case "cbc-plain":
+ block, err := newBlockCipher(key)
+ if err != nil {
+ return nil, fmt.Errorf("initializing decryption: %w", err)
+ }
+ for processed := 0; processed < len(plaintext); processed += sectorSize {
+ blockLeft := sectorSize
+ if processed+blockLeft > len(plaintext) {
+ blockLeft = len(plaintext) - processed
+ }
+ ivValue := processed/sectorSize + ivTweak
+ if bulk { // iv_large_sectors is not being used
+ ivValue *= sectorSize / V1SectorSize
+ }
+ iv0 := make([]byte, block.BlockSize())
+ binary.LittleEndian.PutUint32(iv0, uint32(ivValue))
+ cipher := cipher.NewCBCDecrypter(block, iv0)
+ cipher.CryptBlocks(plaintext[processed:processed+blockLeft], ciphertext[processed:processed+blockLeft])
+ }
+ case "cbc-plain64":
+ block, err := newBlockCipher(key)
+ if err != nil {
+ return nil, fmt.Errorf("initializing decryption: %w", err)
+ }
+ for processed := 0; processed < len(plaintext); processed += sectorSize {
+ blockLeft := sectorSize
+ if processed+blockLeft > len(plaintext) {
+ blockLeft = len(plaintext) - processed
+ }
+ ivValue := processed/sectorSize + ivTweak
+ if bulk { // iv_large_sectors is not being used
+ ivValue *= sectorSize / V1SectorSize
+ }
+ iv0 := make([]byte, block.BlockSize())
+ binary.LittleEndian.PutUint64(iv0, uint64(ivValue))
+ cipher := cipher.NewCBCDecrypter(block, iv0)
+ cipher.CryptBlocks(plaintext[processed:processed+blockLeft], ciphertext[processed:processed+blockLeft])
+ }
+ case "cbc-essiv:sha256":
+ hasherName := strings.TrimPrefix(cipherMode, "cbc-essiv:")
+ hasher, err := hasherByName(hasherName)
+ if err != nil {
+ return nil, fmt.Errorf("initializing decryption using hash %s: %w", hasherName, err)
+ }
+ h := hasher()
+ h.Write(key)
+ makeiv, err := newBlockCipher(h.Sum(nil))
+ if err != nil {
+ return nil, fmt.Errorf("initializing decryption: %w", err)
+ }
+ block, err := newBlockCipher(key)
+ if err != nil {
+ return nil, fmt.Errorf("initializing decryption: %w", err)
+ }
+ for processed := 0; processed < len(plaintext); processed += sectorSize {
+ blockLeft := sectorSize
+ if processed+blockLeft > len(plaintext) {
+ blockLeft = len(plaintext) - processed
+ }
+ ivValue := (processed/sectorSize + ivTweak)
+ if bulk { // iv_large_sectors is not being used
+ ivValue *= sectorSize / V1SectorSize
+ }
+ plain0 := make([]byte, makeiv.BlockSize())
+ binary.LittleEndian.PutUint64(plain0, uint64(ivValue))
+ iv0 := make([]byte, makeiv.BlockSize())
+ makeiv.Encrypt(iv0, plain0)
+ cipher := cipher.NewCBCDecrypter(block, iv0)
+ cipher.CryptBlocks(plaintext[processed:processed+blockLeft], ciphertext[processed:processed+blockLeft])
+ }
+ case "xts-plain":
+ cipher, err := xts.NewCipher(newBlockCipher, key)
+ if err != nil {
+ return nil, fmt.Errorf("initializing decryption: %w", err)
+ }
+ for processed := 0; processed < len(ciphertext); processed += sectorSize {
+ blockLeft := sectorSize
+ if processed+blockLeft > len(ciphertext) {
+ blockLeft = len(ciphertext) - processed
+ }
+ sector := uint64(processed/sectorSize + ivTweak)
+ if bulk { // iv_large_sectors is not being used
+ sector *= uint64(sectorSize / V1SectorSize)
+ }
+ sector = sector % 0x100000000
+ cipher.Decrypt(plaintext[processed:processed+blockLeft], ciphertext[processed:processed+blockLeft], sector)
+ }
+ case "xts-plain64":
+ cipher, err := xts.NewCipher(newBlockCipher, key)
+ if err != nil {
+ return nil, fmt.Errorf("initializing decryption: %w", err)
+ }
+ for processed := 0; processed < len(ciphertext); processed += sectorSize {
+ blockLeft := sectorSize
+ if processed+blockLeft > len(ciphertext) {
+ blockLeft = len(ciphertext) - processed
+ }
+ sector := uint64(processed/sectorSize + ivTweak)
+ if bulk { // iv_large_sectors is not being used
+ sector *= uint64(sectorSize / V1SectorSize)
+ }
+ cipher.Decrypt(plaintext[processed:processed+blockLeft], ciphertext[processed:processed+blockLeft], sector)
+ }
+ default:
+ return nil, fmt.Errorf("unsupported cipher mode %s", cipherMode)
+ }
+
+ if err != nil {
+ return nil, fmt.Errorf("cipher error: %w", err)
+ }
+ return plaintext, nil
+}
+
+func v2encrypt(cipherSuite string, ivTweak int, key []byte, ciphertext []byte, sectorSize int, bulk bool) ([]byte, error) {
+ var cipherName, cipherMode string
+ switch {
+ default:
+ cipherSpec := strings.SplitN(cipherSuite, "-", 2)
+ if len(cipherSpec) < 2 {
+ return nil, fmt.Errorf("unrecognized cipher suite %q", cipherSuite)
+ }
+ cipherName = cipherSpec[0]
+ cipherMode = cipherSpec[1]
+ }
+ return v1encrypt(cipherName, cipherMode, ivTweak, key, ciphertext, sectorSize, bulk)
+}
+
+func v2decrypt(cipherSuite string, ivTweak int, key []byte, ciphertext []byte, sectorSize int, bulk bool) ([]byte, error) {
+ var cipherName, cipherMode string
+ switch {
+ default:
+ cipherSpec := strings.SplitN(cipherSuite, "-", 2)
+ if len(cipherSpec) < 2 {
+ return nil, fmt.Errorf("unrecognized cipher suite %q", cipherSuite)
+ }
+ cipherName = cipherSpec[0]
+ cipherMode = cipherSpec[1]
+ }
+ return v1decrypt(cipherName, cipherMode, ivTweak, key, ciphertext, sectorSize, bulk)
+}
+
+func diffuse(key []byte, h hash.Hash) []byte {
+ sum := make([]byte, len(key))
+ counter := uint32(0)
+ for summed := 0; summed < len(key); summed += h.Size() {
+ h.Reset()
+ var buf [4]byte
+ binary.BigEndian.PutUint32(buf[:], counter)
+ h.Write(buf[:])
+ needed := len(key) - summed
+ if needed > h.Size() {
+ needed = h.Size()
+ }
+ h.Write(key[summed : summed+needed])
+ partial := h.Sum(nil)
+ copy(sum[summed:summed+needed], partial)
+ counter++
+ }
+ return sum
+}
+
+func afMerge(splitKey []byte, h hash.Hash, keysize int, stripes int) ([]byte, error) {
+ if len(splitKey) != keysize*stripes {
+ return nil, fmt.Errorf("expected %d af bytes, got %d", keysize*stripes, len(splitKey))
+ }
+ d := make([]byte, keysize)
+ for i := 0; i < stripes-1; i++ {
+ for j := 0; j < keysize; j++ {
+ d[j] = d[j] ^ splitKey[i*keysize+j]
+ }
+ d = diffuse(d, h)
+ }
+ for j := 0; j < keysize; j++ {
+ d[j] = d[j] ^ splitKey[(stripes-1)*keysize+j]
+ }
+ return d, nil
+}
+
+func afSplit(key []byte, h hash.Hash, stripes int) ([]byte, error) {
+ keysize := len(key)
+ s := make([]byte, keysize*stripes)
+ d := make([]byte, keysize)
+ n, err := rand.Read(s[0 : (keysize-1)*stripes])
+ if err != nil {
+ return nil, err
+ }
+ if n != (keysize-1)*stripes {
+ return nil, fmt.Errorf("short read when attempting to read random data: %d < %d", n, (keysize-1)*stripes)
+ }
+ for i := 0; i < stripes-1; i++ {
+ for j := 0; j < keysize; j++ {
+ d[j] = d[j] ^ s[i*keysize+j]
+ }
+ d = diffuse(d, h)
+ }
+ for j := 0; j < keysize; j++ {
+ s[(stripes-1)*keysize+j] = d[j] ^ key[j]
+ }
+ return s, nil
+}
+
+func roundUpToMultiple(i, factor int) int {
+ if i < 0 {
+ return 0
+ }
+ if factor < 1 {
+ return i
+ }
+ return i + ((factor - (i % factor)) % factor)
+}
+
+func roundDownToMultiple(i, factor int) int {
+ if i < 0 {
+ return 0
+ }
+ if factor < 1 {
+ return i
+ }
+ return i - (i % factor)
+}
+
+func hasherByName(name string) (func() hash.Hash, error) {
+ switch name {
+ case "sha1":
+ return sha1.New, nil
+ case "sha256":
+ return sha256.New, nil
+ case "sha512":
+ return sha512.New, nil
+ case "ripemd160":
+ return ripemd160.New, nil
+ default:
+ return nil, fmt.Errorf("unsupported digest algorithm %q", name)
+ }
+}
+
+type wrapper struct {
+ fn func(plaintext []byte) ([]byte, error)
+ blockSize int
+ buf []byte
+ buffered int
+ processed int
+ reader io.Reader
+ eof bool
+ writer io.Writer
+}
+
+func (w *wrapper) partialWrite() error {
+ if w.buffered-w.processed >= w.blockSize {
+ toProcess := roundDownToMultiple(w.buffered-w.processed, w.blockSize)
+ processed, err := w.fn(w.buf[w.processed : w.processed+toProcess])
+ if err != nil {
+ return err
+ }
+ nProcessed := copy(w.buf[w.processed:], processed)
+ w.processed += nProcessed
+ }
+ if w.processed >= w.blockSize {
+ nWritten, err := w.writer.Write(w.buf[:w.processed])
+ if err != nil {
+ return err
+ }
+ copy(w.buf, w.buf[nWritten:w.buffered])
+ w.buffered -= nWritten
+ w.processed -= nWritten
+ if w.processed != 0 {
+ return fmt.Errorf("short write: %d != %d", nWritten, nWritten+w.processed)
+ }
+ }
+ return nil
+}
+
+func (w *wrapper) Write(buf []byte) (int, error) {
+ n := 0
+ for n < len(buf) {
+ nBuffered := copy(w.buf[w.buffered:], buf[n:])
+ w.buffered += nBuffered
+ n += nBuffered
+ if err := w.partialWrite(); err != nil {
+ return n, err
+ }
+ }
+ return n, nil
+}
+
+func (w *wrapper) Read(buf []byte) (int, error) {
+ n := 0
+ for n < len(buf) {
+ if !w.eof {
+ nRead, err := w.reader.Read(w.buf[w.buffered:])
+ if err != nil {
+ if !errors.Is(err, io.EOF) {
+ w.buffered += nRead
+ return n, err
+ }
+ w.eof = true
+ }
+ w.buffered += nRead
+ }
+ if w.buffered == 0 && w.eof {
+ return n, io.EOF
+ }
+ if w.buffered-w.processed >= w.blockSize {
+ toProcess := roundDownToMultiple(w.buffered-w.processed, w.blockSize)
+ processed, err := w.fn(w.buf[w.processed : w.processed+toProcess])
+ if err != nil {
+ return n, err
+ }
+ nProcessed := copy(w.buf[w.processed:], processed)
+ w.processed += nProcessed
+ }
+ nRead := copy(buf[n:], w.buf[:w.processed])
+ n += nRead
+ copy(w.buf, w.buf[nRead:w.buffered])
+ w.processed -= nRead
+ w.buffered -= nRead
+ if w.buffered-w.processed < w.blockSize {
+ break
+ }
+ }
+ return n, nil
+}
+
+func (w *wrapper) Close() error {
+ if w.writer != nil {
+ if w.buffered%w.blockSize != 0 {
+ nPadding := w.blockSize - w.buffered%w.blockSize
+ nWritten, err := w.Write(make([]byte, nPadding))
+ if err != nil {
+ return fmt.Errorf("flushing write: %v", err)
+ }
+ if nWritten < nPadding {
+ return fmt.Errorf("flushing write: %d != %d", nPadding, nWritten)
+ }
+ }
+ }
+ return nil
+}
+
+// EncryptWriter creates an io.WriteCloser which buffers writes through an
+// encryption function, transforming and writing multiples of the blockSize.
+// After writing a final block, the returned writer should be closed.
+// If only a partial block has been written when Close() is called, a final
+// block with its length padded with zero bytes will be transformed and
+// written.
+func EncryptWriter(fn func(plaintext []byte) ([]byte, error), writer io.Writer, blockSize int) io.WriteCloser {
+ bufferSize := roundUpToMultiple(1024*1024, blockSize)
+ return &wrapper{fn: fn, blockSize: blockSize, buf: make([]byte, bufferSize), writer: writer}
+}
+
+// DecryptReader creates an io.ReadCloser which buffers reads through a
+// decryption function, decrypting and returning multiples of the blockSize
+// until it reaches the end of the file. When data will no longer be read, the
+// returned reader should be closed.
+func DecryptReader(fn func(ciphertext []byte) ([]byte, error), reader io.Reader, blockSize int) io.ReadCloser {
+ bufferSize := roundUpToMultiple(1024*1024, blockSize)
+ return &wrapper{fn: fn, blockSize: blockSize, buf: make([]byte, bufferSize), reader: reader}
+}