This Go code implements a 256-bit asymmetric encryption scheme using Elliptic Curve Cryptography (ECC). It allows for encrypting and decrypting data using the NIST P-256 elliptic curve. During encryption, a random value is chosen, and the resulting point is computed. The ciphertext is formed from the coordinates of this point, along with the hash of the message. Decryption involves computing another point, from which the session key is derived, and then decrypting the ciphertext using an XOR operation. Finally, integrity is verified by comparing the hash of the recovered message with the previously received hash.
• Select a random value \( k \) from the interval \( [1, n-1] \), where \( n \) is the order of the cyclic group generated by the elliptic curve.
• Compute the point \( k \cdot G \), where \( G \) is the base point on the curve, resulting in \( (k \cdot G)_x \) and \( (k \cdot G)_y \).
• Compute the hash \( H(m) \) of the message \( m \).
• Compute the ciphertext consisting of the coordinates \( (x_1, y_1) \) of the point \( k \cdot G \), the hash \( H(m) \), and the derived key.
• The ciphertext organization alternates between \( C_1C_3C_2 \) and \( C_1C_2C_3 \) depending on the chosen mode.
• Compute the point \( x \cdot P \), where \( P \) are the received coordinates and \( x \) is the \( x \) coordinate of the corresponding private key \( d \).
• Derive the session key from the coordinates \( x_2 \) and \( y_2 \) of the point \( x \cdot P \).
• Decrypt the ciphertext by performing an XOR operation between the ciphertext bytes and the session key.
• Verify the integrity by checking if the hash of the recovered message matches the previously received hash.
It outlines the key steps involved in both encryption and decryption, including the generation of random values, computation of points on the elliptic curve, hashing of messages, construction of ciphertext, derivation of session keys, decryption process, and integrity verification.
To install the library, save the following code to the path go/src/crypto/ecdsa/eccrypt.go
.
package ecdsa import ( "bytes" "crypto/sha256" "encoding/asn1" "encoding/binary" "errors" "io" "math/big" ) // ecCipher represents a structure for holding coordinates of a point // on the elliptic curve, the hash of the message, and the ciphertext. type ecCipher struct { XCoordinate *big.Int YCoordinate *big.Int HASH []byte CipherText []byte } // EncryptAsn1 encrypts data using ASN.1 encoding. func (pub *PublicKey) EncryptAsn1(data []byte, random io.Reader) ([]byte, error) { return EncryptAsn1(pub, data, random) } // DecryptAsn1 decrypts data using ASN.1 encoding. func (priv *PrivateKey) DecryptAsn1(data []byte) ([]byte, error) { return DecryptAsn1(priv, data) } // EncryptAsn1 encrypts data with ASN.1 encoding. func EncryptAsn1(pub *PublicKey, data []byte, rand io.Reader) ([]byte, error) { cipher, err := Encrypt(pub, data, rand, 0) if err != nil { return nil, err } return CipherMarshal(cipher) } // DecryptAsn1 decrypts data with ASN.1 encoding. func DecryptAsn1(pub *PrivateKey, data []byte) ([]byte, error) { cipher, err := CipherUnmarshal(data) if err != nil { return nil, err } return Decrypt(pub, cipher, 0) } // CipherMarshal encodes the cipher structure into ASN.1 format. func CipherMarshal(data []byte) ([]byte, error) { data = data[1:] x := new(big.Int).SetBytes(data[:32]) y := new(big.Int).SetBytes(data[32:64]) hash := data[64:96] cipherText := data[96:] return asn1.Marshal(ecCipher{x, y, hash, cipherText}) } // CipherUnmarshal decodes data from ASN.1 format into the cipher structure. func CipherUnmarshal(data []byte) ([]byte, error) { var cipher ecCipher _, err := asn1.Unmarshal(data, &cipher) if err != nil { return nil, err } x := cipher.XCoordinate.Bytes() y := cipher.YCoordinate.Bytes() hash := cipher.HASH cipherText := cipher.CipherText if n := len(x); n < 32 { x = append(zeroByteSlice()[:32-n], x...) } if n := len(y); n < 32 { y = append(zeroByteSlice()[:32-n], y...) } c := []byte{} c = append(c, x...) c = append(c, y...) c = append(c, hash...) c = append(c, cipherText...) return append([]byte{0x04}, c...), nil } // Encrypt encrypts data using ECC. func Encrypt(pub *PublicKey, data []byte, random io.Reader, mode int) ([]byte, error) { length := len(data) for { c := []byte{} curve := pub.Curve k, err := randFieldElement(curve, random) if err != nil { return nil, err } x1, y1 := curve.ScalarBaseMult(k.Bytes()) x2, y2 := curve.ScalarMult(pub.X, pub.Y, k.Bytes()) x1Buf := x1.Bytes() y1Buf := y1.Bytes() x2Buf := x2.Bytes() y2Buf := y2.Bytes() if n := len(x1Buf); n < 32 { x1Buf = append(zeroByteSlice()[:32-n], x1Buf...) } if n := len(y1Buf); n < 32 { y1Buf = append(zeroByteSlice()[:32-n], y1Buf...) } if n := len(x2Buf); n < 32 { x2Buf = append(zeroByteSlice()[:32-n], x2Buf...) } if n := len(y2Buf); n < 32 { y2Buf = append(zeroByteSlice()[:32-n], y2Buf...) } c = append(c, x1Buf...) c = append(c, y1Buf...) tm := []byte{} tm = append(tm, x2Buf...) tm = append(tm, data...) tm = append(tm, y2Buf...) Sum256 := func(msg []byte) []byte { res := sha256.New() res.Write(msg) hash := res.Sum(nil) return []byte(hash) } h := Sum256(tm) c = append(c, h...) ct, ok := ikdf(length, x2Buf, y2Buf) if !ok { continue } c = append(c, ct...) for i := 0; i < length; i++ { c[96+i] ^= data[i] } switch mode { case 0: return append([]byte{0x04}, c...), nil case 1: c1 := make([]byte, 64) c2 := make([]byte, len(c)-96) c3 := make([]byte, 32) copy(c1, c[:64]) copy(c3, c[64:96]) copy(c2, c[96:]) ciphertext := []byte{} ciphertext = append(ciphertext, c1...) ciphertext = append(ciphertext, c2...) ciphertext = append(ciphertext, c3...) return append([]byte{0x04}, ciphertext...), nil default: return append([]byte{0x04}, c...), nil } } } // Decrypt decrypts data using ECC. func Decrypt(priv *PrivateKey, data []byte, mode int) ([]byte, error) { switch mode { case 0: data = data[1:] case 1: data = data[1:] c1 := make([]byte, 64) c2 := make([]byte, len(data)-96) c3 := make([]byte, 32) copy(c1, data[:64]) //x1,y1 copy(c2, data[64:len(data)-32]) copy(c3, data[len(data)-32:]) c := []byte{} c = append(c, c1...) c = append(c, c3...) c = append(c, c2...) data = c default: data = data[1:] } length := len(data) - 96 curve := priv.Curve x := new(big.Int).SetBytes(data[:32]) y := new(big.Int).SetBytes(data[32:64]) x2, y2 := curve.ScalarMult(x, y, priv.D.Bytes()) x2Buf := x2.Bytes() y2Buf := y2.Bytes() if n := len(x2Buf); n < 32 { x2Buf = append(zeroByteSlice()[:32-n], x2Buf...) } if n := len(y2Buf); n < 32 { y2Buf = append(zeroByteSlice()[:32-n], y2Buf...) } c, ok := ikdf(length, x2Buf, y2Buf) if !ok { return nil, errors.New("Decrypt: failed to decrypt") } for i := 0; i < length; i++ { c[i] ^= data[i+96] } tm := []byte{} tm = append(tm, x2Buf...) tm = append(tm, c...) tm = append(tm, y2Buf...) Sum256 := func(msg []byte) []byte { res := sha256.New() res.Write(msg) hash := res.Sum(nil) return []byte(hash) } h := Sum256(tm) if bytes.Compare(h, data[64:96]) != 0 { return c, errors.New("Decrypt: failed to decrypt") } return c, nil } // intToBytes converts an integer to a byte slice. func intToBytes(x int) []byte { var buf = make([]byte, 4) binary.BigEndian.PutUint32(buf, uint32(x)) return buf } // ikdf is a Key Derivation Function (KDF). func ikdf(length int, x ...[]byte) ([]byte, bool) { var c []byte ct := 1 h := sha256.New() for i, j := 0, (length+31)/32; i < j; i++ { h.Reset() for _, xx := range x { h.Write(xx) } h.Write(intToBytes(ct)) hash := h.Sum(nil) if i+1 == j && length%32 != 0 { c = append(c, hash[:length%32]...) } else { c = append(c, hash...) } ct++ } for i := 0; i < length; i++ { if c[i] != 0 { return c, true } } return c, false } // zeroByteSlice returns a slice of zero bytes. func zeroByteSlice() []byte { return []byte{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, } }
package main import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "fmt" "log" ) func main() { // Generate ECDSA keys privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { log.Fatal("Failed to generate private key:", err) } // Encrypt and decrypt using the generated keys ciphertxt, err := ecdsa.EncryptAsn1(privateKey.Public().(*ecdsa.PublicKey), []byte("message"), rand.Reader) if err != nil { log.Fatal("Failed to encrypt the message:", err) } fmt.Print("CipherText: ") fmt.Printf("%x\n", ciphertxt) plaintxt, err := ecdsa.DecryptAsn1(privateKey, ciphertxt) if err != nil { log.Fatal("Failed to decrypt the ciphertext:", err) } fmt.Print("PlainText: ") fmt.Printf("%s\n\n", plaintxt) }
This function is compatible with ECDSA, SM2, and NUMS curves. With the advantage that NUMS Twisted Edwards is resistant to side-channel attacks.
./edgetk -pkey keygen -algorithm ecdsa [-priv Private.pem] [-pass "pass"] [-pub Public.pem]
./edgetk -pkey encrypt -algorithm ecdsa -key Public.pem inputfile.ext > cipher.txt
./edgetk -pkey decrypt -algorithm ecdsa -key Private.pem cipher.txt > plaintext.ext
./edgetk -pkey text -key Private.pem
./edgetk -pkey text -key Public.pem
Copyright (c) 2021 Pedro F. Albanese <pedroalbanese@hotmail.com>
Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.