From 1b5570c74dc84b97653e267f3cb0f938b5bf4b80 Mon Sep 17 00:00:00 2001 From: Sergey Matveev Date: Fri, 8 Dec 2023 12:21:21 +0300 Subject: [PATCH] 28147-89 and CryptoPro key wrapping support --- README | 1 + gost28147/wrap.go | 83 ++++++++++++++++++++++++++++++++++++++++++ gost28147/wrap_test.go | 30 +++++++++++++++ news.texi | 5 +++ 4 files changed, 119 insertions(+) create mode 100644 gost28147/wrap.go create mode 100644 gost28147/wrap_test.go diff --git a/README b/README index 05dd222..2085a18 100644 --- a/README +++ b/README @@ -14,6 +14,7 @@ GOST is GOvernment STandard of Russian Federation (and Soviet Union). vice versa * VKO GOST R 34.10-2001 key agreement function (RFC 4357) * VKO GOST R 34.10-2012 key agreement function (RFC 7836) +* 28147-89 and CryptoPro key wrapping (RFC 4357) * KDF_GOSTR3411_2012_256 KDF function (RFC 7836) * GOST R 34.12-2015 128-bit block cipher Кузнечик (Kuznechik) (RFC 7801) * GOST R 34.12-2015 64-bit block cipher Магма (Magma) diff --git a/gost28147/wrap.go b/gost28147/wrap.go new file mode 100644 index 0000000..24190ea --- /dev/null +++ b/gost28147/wrap.go @@ -0,0 +1,83 @@ +// GoGOST -- Pure Go GOST cryptographic functions library +// Copyright (C) 2015-2023 Sergey Matveev +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 of the License. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package gost28147 + +import ( + "bytes" + "crypto/subtle" + "encoding/binary" +) + +func WrapGost(ukm, kek, cek []byte) []byte { + c := NewCipher(kek, &SboxIdGost2814789CryptoProAParamSet) + mac, err := c.NewMAC(4, ukm) + if err != nil { + panic(err) + } + _, err = mac.Write(cek) + if err != nil { + panic(err) + } + cekMac := mac.Sum(nil) + cekEnc := make([]byte, 32) + c.NewECBEncrypter().CryptBlocks(cekEnc, cek) + return bytes.Join([][]byte{ukm, cekEnc, cekMac}, nil) +} + +func UnwrapGost(kek, data []byte) []byte { + ukm, data := data[:8], data[8:] + cekEnc, cekMac := data[:KeySize], data[KeySize:] + c := NewCipher(kek, &SboxIdGost2814789CryptoProAParamSet) + cek := make([]byte, 32) + c.NewECBDecrypter().CryptBlocks(cek, cekEnc) + mac, err := c.NewMAC(4, ukm) + if err != nil { + panic(err) + } + _, err = mac.Write(cek) + if err != nil { + panic(err) + } + if subtle.ConstantTimeCompare(mac.Sum(nil), cekMac) != 1 { + return nil + } + return cek +} + +func DiversifyCryptoPro(kek, ukm []byte) []byte { + out := kek + for i := 0; i < 8; i++ { + var s1, s2 uint64 + for j := 0; j < 8; j++ { + k := binary.LittleEndian.Uint32(out[j*4 : j*4+4]) + if (ukm[i]>>j)&1 > 0 { + s1 += uint64(k) + } else { + s2 += uint64(k) + } + } + iv := make([]byte, 8) + binary.LittleEndian.PutUint32(iv[:4], uint32(s1%(1<<32))) + binary.LittleEndian.PutUint32(iv[4:], uint32(s2%(1<<32))) + c := NewCipher(out, &SboxIdGost2814789CryptoProAParamSet) + c.NewCFBEncrypter(iv).XORKeyStream(out, out) + } + return out +} + +func UnwrapCryptoPro(kek, data []byte) []byte { + return UnwrapGost(DiversifyCryptoPro(kek, data[:8]), data) +} diff --git a/gost28147/wrap_test.go b/gost28147/wrap_test.go new file mode 100644 index 0000000..9cf066b --- /dev/null +++ b/gost28147/wrap_test.go @@ -0,0 +1,30 @@ +package gost28147 + +import ( + "bytes" + "crypto/rand" + "io" + "testing" +) + +func TestWrapSymmetric(t *testing.T) { + kek := make([]byte, KeySize) + cek := make([]byte, KeySize) + ukm := make([]byte, 8) + for i := 0; i < 1000; i++ { + if _, err := io.ReadFull(rand.Reader, kek); err != nil { + t.Fatal(err) + } + if _, err := io.ReadFull(rand.Reader, cek); err != nil { + t.Fatal(err) + } + if _, err := io.ReadFull(rand.Reader, ukm); err != nil { + t.Fatal(err) + } + data := WrapGost(ukm, kek, cek) + got := UnwrapGost(kek, data) + if !bytes.Equal(got, cek) { + t.FailNow() + } + } +} diff --git a/news.texi b/news.texi index a514e52..fdca692 100644 --- a/news.texi +++ b/news.texi @@ -3,6 +3,11 @@ @table @strong +@anchor{Release 5.14.0} +@item 5.14.0 +28147-89 and CryptoPro key wrapping support +(@url{https://tools.ietf.org/html/rfc4357.html, RFC 4357}). + @anchor{Release 5.13.0} @item 5.13.0 @itemize -- 2.44.0