init: v1.0.0

This commit is contained in:
yaole
2026-05-27 23:03:00 +08:00
commit 8d97f750eb
466 changed files with 80067 additions and 0 deletions
+52
View File
@@ -0,0 +1,52 @@
package fpe
import (
"errors"
"xdx.jelly/xgcl/sm/sm4"
)
// return the check code of id.
func checkCodeOfID(id0 string) byte {
panic("todo")
}
// EncryptID 加密身份证号。最后一位为校验位。
// key为SM4密钥,16字节
// tweak随机数,任意长度,可明文保存。
func EncryptID(id string, key []byte, tweak []byte) (string, error) {
if len(id) != 18 {
return "", errors.New("wrong id")
}
if len(key) != 16 {
return "", errors.New("wrong key")
}
b, err := sm4.NewCipher(key)
if err != nil {
return "", err
}
f := NewFF1(b, Numeric)
numerals, err := f.Encode(id[:len(id)-1])
if err != nil {
return "", err
}
cipher, err := f.Encrypt(tweak, numerals)
if err != nil {
return "", err
}
encryptedID, err := f.Decode(cipher)
if err != nil {
return "", err
}
// TODO: add the check code
return encryptedID, nil
}
// 加密纯数字的手机号
func EncryptPhoneNumber(phoneNumber string, key []byte, tweak []byte) (string, error) {
panic("todo")
}
+134
View File
@@ -0,0 +1,134 @@
package fpe
import (
"crypto/cipher"
"errors"
"math"
"math/big"
"xdx.jelly/xgcl/internal/xor"
)
func rev[T any](X []T) {
l := len(X) / 2
for i := 0; i < l; i++ {
t := X[i]
X[i] = X[l-i-1]
X[l-i-1] = t
}
}
// in = in0||in1||in2...
// out0 = Enc(iv ^ in0)
// out1 = Enc(out0 ^ in1)
// ...
// if iv == nil <==> iv = 0^16.
func prf(res []byte, b cipher.Block, iv []byte, in []byte) []byte {
xor.XorBytes(res, iv, in[:16])
b.Encrypt(res, res)
in = in[16:]
for len(in) > 0 {
xor.XorBytes(res, res, in[:16])
b.Encrypt(res, res)
in = in[16:]
}
return res
}
type FF1 struct {
cipher.Block
Alphabet
}
func NewFF1(b cipher.Block, a Alphabet) FPE {
return &FF1{
Block: b,
Alphabet: a,
}
}
func (f *FF1) round(P []byte, Q []byte, S []byte, A []Numeral, B []Numeral, i int, b int, d int, module *big.Int, isenc bool) {
Q[len(Q)-b-1] = byte(i)
f.Num(B).FillBytes(Q[len(Q)-b:])
prf(S, f.Block, P, Q)
for j := 1; j < (d+15)/16; j++ {
Sj := S[16*j : 16*j+16]
S[16*j+12] = byte(j >> 24)
S[16*j+13] = byte(j >> 16)
S[16*j+14] = byte(j >> 8)
S[16*j+15] = byte(j)
xor.XorBytes(Sj, S[:16], Sj)
f.Block.Encrypt(Sj, Sj)
}
y := new(big.Int).SetBytes(S[:d])
c := f.Num(A)
if isenc {
c.Add(c, y).Mod(c, module)
} else {
c.Sub(c, y).Mod(c, module)
}
f.Str(A, c)
}
func (f *FF1) endecrypt(T []byte, X []Numeral, isEnc bool) ([]Numeral, error) {
n := len(X)
if n <= 2 {
return nil, errors.New("input numeral string X must at least 2 characters")
}
radix := f.Alphabet.Radix()
t := len(T)
u := n / 2
v := n - u
b := (int(math.Ceil(float64(v)*math.Log2(float64(radix)))) + 7) >> 3
d := (b + 7) & (^3)
P := []byte{
1, 2, 1,
0, byte(radix >> 8), byte(radix),
10, byte(u),
byte(n >> 24), byte(n >> 16), byte(n >> 8), byte(n),
byte(t >> 24), byte(t >> 16), byte(t >> 8), byte(t),
}
f.Block.Encrypt(P, P)
// make a copy of X
res := make([]Numeral, len(X))
copy(res, X)
A := res[:u]
B := res[u:]
// Q := append(make([]byte, 0, (b+t+1+15)&(^15)), T...)
Q := make([]byte, (b+t+1+15)&(^15))
copy(Q, T)
S := make([]byte, (d+15)&(^15))
var moduleA, moduleB *big.Int
radixBig := big.NewInt(int64(radix))
// 如果输入X很大,这里的module会很大。
moduleA = new(big.Int).Exp(radixBig, big.NewInt(int64(u)), nil)
moduleB = moduleA
if v > u {
moduleB = radixBig.Mul(moduleB, radixBig)
}
if isEnc {
for i := 0; i < 10; i += 2 {
f.round(P, Q, S, A, B, i, b, d, moduleA, true)
f.round(P, Q, S, B, A, i+1, b, d, moduleB, true)
}
} else {
for i := 9; i >= 0; i -= 2 {
f.round(P, Q, S, B, A, i, b, d, moduleB, false)
f.round(P, Q, S, A, B, i-1, b, d, moduleA, false)
}
}
return res, nil
}
func (f *FF1) Encrypt(T []byte, X []Numeral) ([]Numeral, error) {
return f.endecrypt(T, X, true)
}
func (f FF1) Decrypt(T []byte, X []Numeral) ([]Numeral, error) {
return f.endecrypt(T, X, false)
}
+171
View File
@@ -0,0 +1,171 @@
// fpe is the Format-Preserving Encryption.
package fpe
import (
"errors"
"fmt"
"math/big"
"strings"
)
type Numeral = uint16
type Char = uint16
// Alphabet is the interface for an alphabet.
type Alphabet interface {
// fpe实际上是可对任意的字符表进行编码,比如JPEG2000,编码范围为0xff8f的序列,且不以0xff结尾。
// 但是这里的Encode只处理utf-8的string
Encode(s string) ([]Numeral, error)
Decode(numString []Numeral) (string, error)
// 如果是二进制类的数据
// EncodeBlob(b []byte) ([]Numeral, error)
// DecodeBlob(numString []Numeral) ([]byte, error)
Radix() int
Num(X []Numeral) *big.Int
// X = Str_radix^m(C)
Str(X []Numeral, x *big.Int)
}
var (
// ASCII code 32-126, all printable characters.
Printable = NewAlphabet(" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~")
Numeric = NewAlphabet("0123456789")
Lower = NewAlphabet("abcdefghijklmnopqrstuvwxyz")
Upper = NewAlphabet("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
Alpha = NewAlphabet("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
NumAlpha = NewAlphabet("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
)
type FPE interface {
Encrypt(tweak []byte, X []Numeral) ([]Numeral, error)
Decrypt(tweak []byte, X []Numeral) ([]Numeral, error)
Alphabet
}
// GenericAlphabet 可以处理asicc字符集
type GenericAlphabet struct {
tblBuf [128]Char
tbl [][]Numeral
r Radix
}
// Assume the characters in alphabet are all ASCII now.
// Unicode characters not supported now.
func NewAlphabet(alphabet string) Alphabet {
r := len(alphabet)
res := &GenericAlphabet{}
res.r.Set(r)
tblBuf := res.tblBuf
for i := range tblBuf {
tblBuf[i] = 128
}
for _, d := range alphabet {
if d > 127 {
panic("only ASCII characters are supported now")
}
tblBuf[d] = Char(d)
}
tbls := make([][]Numeral, 0)
start := -1
for i, d := range tblBuf {
switch {
case d == 128:
if start >= 0 {
tbls = append(tbls, tblBuf[start:i])
start = -1
}
case d < 128:
if start < 0 {
start = i
}
}
}
res.tbl = tbls
return res
}
var numSwitch = false
// Num implements Alphabet.
func (ga *GenericAlphabet) Num(X []Char) *big.Int {
return ga.r.Num(X)
}
// x should < radix^(lenX)
func (ga *GenericAlphabet) Str(X []Numeral, x *big.Int) {
ga.r.Str(X, x)
}
// FromString implements Alphabet.
func (ga *GenericAlphabet) Encode(s string) ([]Numeral, error) {
res := make([]Numeral, 0, len(s))
for _, c := range s {
if c>>16 != 0 {
return nil, fmt.Errorf("unsupported character %c", c)
}
if idx := index(Char(c), ga.tbl); idx >= 0 {
res = append(res, Numeral(idx))
} else {
return nil, errors.New("bad characters")
}
}
return res, nil
}
// // Radix implements Alphabet.
func (ga *GenericAlphabet) Radix() int {
return int(ga.r.r)
}
// ToString implements Alphabet.
func (ga *GenericAlphabet) Decode(numString []Numeral) (string, error) {
var sb strings.Builder
radix := uint16(ga.r.r)
for _, d := range numString {
if d >= radix {
return "", errors.New("bad numeric string")
}
if _, err := sb.WriteRune(rune(char(d, ga.tbl))); err != nil {
return "", errors.New("bad rune")
}
}
return sb.String(), nil
}
var _ Alphabet = &GenericAlphabet{}
// tbls[i] are continuous Char
// tbls[0][0], tbls[0][1], ..., tbls[0][n0],
// tbls[1][0], tbls[1][1], ..., tbls[1][n1],
// ...
func index(c Char, tbls [][]Char) int {
n := 0
for _, tbl := range tbls {
n0 := int(c) - int(tbl[0])
if n0 >= 0 && n0 < len(tbl) {
return n + n0
}
n += len(tbl)
}
return -1
}
// Assume n < radix
func char(n Numeral, tbls [][]Char) Char {
m := int(n)
for _, tbl := range tbls {
if m < len(tbl) {
return tbl[m]
}
m -= len(tbl)
}
panic("numeric great than radix")
}
+182
View File
@@ -0,0 +1,182 @@
package fpe
import (
"fmt"
"math/rand"
"testing"
"time"
"github.com/stretchr/testify/assert"
"xdx.jelly/xgcl/grand"
"xdx.jelly/xgcl/sm/sm4"
)
func TestNum(t *testing.T) {
N := 1000
A := make([]Numeral, N)
B := make([]Numeral, N)
for r := 2; r < 100; r++ {
radix := NewRadix(r)
for i := 1; i <= N; i++ {
for j := 0; j < i; j++ {
A[j] = Numeral(rand.Int31() % int32(r))
}
numSwitch = false
a0 := radix.Num(A[:i])
numSwitch = true
a1 := radix.Num(A[:i])
assert.Equal(t, a0.Cmp(a1), 0)
radix.Str(B[:i], a0)
assert.Equal(t, A[:i], B[:i])
}
}
}
func TestStr(t *testing.T) {
f := Alpha
// 24948832942366750129003083595837476696096111951836798179448429117431251233398075403977925597490
A := []Numeral{
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
}
for i := 20; i <= len(A); i++ {
a := f.Num(A[:i])
B := make([]Numeral, i)
f.Str(B, a)
for j := range B {
if B[j] != A[j] {
t.Fatal()
}
}
}
}
// BenchmarkAlphabet-10 1158810 1020 ns/op 88 B/op 3 allocs/op
// BenchmarkAlphabet-10 3776529 310.4 ns/op 96 B/op 4 allocs/op
func BenchmarkNum(b *testing.B) {
f := Alpha
A := []Numeral{
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
f.Num(A)
}
}
// BenchmarkStr-10 556981 2130 ns/op 104 B/op 2 allocs/op
// BenchmarkStr-10 3134851 382.2 ns/op 104 B/op 2 allocs/op
func BenchmarkStr(b *testing.B) {
f := Alpha
A := []Numeral{
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
}
a := f.Num(A)
B := make([]Numeral, len(A))
b.ResetTimer()
for i := 0; i < b.N; i++ {
f.Str(B, a)
}
}
func TestFF1(t *testing.T) {
// fmt.Printf("%x\n", ^uint64(15))
// 密钥
key := grand.GetRandom(16)
// 使用SM4加密算法
b, _ := sm4.NewCipher(key)
// tweak
T := grand.GetRandom(16)
// interface FPE
f := NewFF1(b, NewAlphabet("abcdefghijklmnopqrstuvwxyz "))
plainString0 := "hello world"
plain0, err := f.Encode(plainString0)
assert.Nil(t, err)
cipher, err := f.Encrypt(T, plain0)
assert.Nil(t, err)
cipherString, err := f.Decode(cipher)
assert.Nil(t, err)
fmt.Println("Cipher string:", cipherString)
plain1, err := f.Decrypt(T, cipher)
assert.Nil(t, err)
assert.Equal(t, plain0, plain1)
plainString1, err := f.Decode(plain1)
assert.Nil(t, err)
fmt.Println("Decrypt string:", plainString1)
assert.Equal(t, plainString0, plainString1)
}
// BenchmarkFF1-10 129781 8683 ns/op 4256 B/op 154 allocs/op
// BenchmarkFF1-10 325104 3694 ns/op 2808 B/op 99 allocs/op
func BenchmarkFF1(b *testing.B) {
key := grand.GetRandom(16)
block, _ := sm4.NewCipher(key)
T := grand.GetRandom(16)
f := NewFF1(block, Printable)
plainString0 := "hello world"
plain0, _ := f.Encode(plainString0)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = f.Encrypt(T, plain0)
}
}
// // 密文长度 加密速率(MBps)
// // 32 2.55
// // 64 3.99
// // 128 3.58
// // 256 3.82
// // 512 3.65
// // 1024 3.15
// // 2048 2.38
// // 4096 1.58
// // 8192 0.94
func TestSpeed(t *testing.T) {
key := grand.GetRandom(16)
block, _ := sm4.NewCipher(key)
T := grand.GetRandom(16)
f := NewFF1(block, Printable)
fmt.Println("密文长度 加密速率(MBps)")
for plainLen, cnt := 32, 10000; plainLen < 10000; plainLen, cnt = plainLen*2, cnt/2 {
b := make([]byte, plainLen)
for i := range b {
b[i] = byte(rand.Uint32()%26 + 'a')
}
plain := string(b)
start := time.Now()
for i := 0; i < cnt; i++ {
n, _ := f.Encode(plain)
_, _ = f.Encrypt(T, n)
}
end := time.Now()
elapsed := end.Sub(start)
fmt.Printf("%4d %.2f\n", plainLen, float64(len(plain)*cnt)/(1024*1024*elapsed.Seconds()))
}
}
+125
View File
@@ -0,0 +1,125 @@
package fpe
import (
"math"
"math/big"
"math/bits"
)
type Radix struct {
r int
e int // the maximum number such that r^e is a uint64
rBig *big.Int // radix as big
reBig *big.Int // radix^e as big.Int, the same as rPowers[-1]
rPowers []big.Int // [r, r^2, ..., r^e]
}
func NewRadix(r int) *Radix {
radix := &Radix{}
radix.Set(r)
return radix
}
func (r *Radix) Set(v int) {
if v < 2 || v >= math.MaxUint16 {
panic("radix overflow")
}
r.r = v
r.e = 64 / bits.Len32(uint32(v))
r.rPowers = make([]big.Int, r.e)
r.rPowers[0].SetUint64(uint64(v))
for i, vi := 1, uint64(v); i < r.e; i++ {
vi *= uint64(v)
r.rPowers[i].SetUint64(vi)
}
r.rBig = &r.rPowers[0]
r.reBig = &r.rPowers[r.e-1]
}
func (r *Radix) Radix() int {
return r.r
}
func (r *Radix) Num(X []Char) *big.Int {
if false {
radix := big.NewInt(int64(r.r))
n := new(big.Int)
for _, d := range X {
n.Mul(n, radix)
n.Add(n, big.NewInt(int64(d)))
}
return n
} else {
radix := uint64(r.r)
l := len(X) % r.e
tail := uint64(0)
for i := 0; i < l; i++ {
tail *= uint64(radix)
tail += uint64(X[i])
}
n := new(big.Int).SetUint64(tail)
for i := l; i < len(X); i += r.e {
d := uint64(0)
for j := 0; j < r.e; j++ {
d = d*uint64(radix) + uint64(X[i+j])
}
n.Mul(n, r.reBig)
n.Add(n, new(big.Int).SetUint64(d))
}
return n
}
}
// x should < radix^(lenX)
func (r *Radix) Str(X []Numeral, x *big.Int) {
if false {
n := len(X)
radix := big.NewInt(int64(r.r))
d := new(big.Int)
xx := new(big.Int).Set(x) // make a copy
for i := 0; i < n; i++ {
xx, d = xx.DivMod(xx, radix, d)
X[n-i-1] = Numeral(d.Int64())
}
// if xx is not zero, then the input x must too big
if xx.Sign() != 0 {
panic("x should less then radix^m")
}
} else {
xx := new(big.Int).Set(x) // make a copy
lx := len(X)
radix := uint64(r.r)
d := new(big.Int)
// the tail part
m := lx % r.e
if m > 0 {
xx, d = xx.DivMod(xx, &r.rPowers[m-1], d)
n := d.Uint64()
for i := lx - 1; i >= lx-m; i-- {
X[i] = Numeral(n % radix)
n /= radix
}
}
for i := lx - m; i > 0; i -= r.e {
xx, d = xx.DivMod(xx, r.reBig, d)
n := d.Uint64()
for j := i - 1; j >= i-r.e; j-- {
X[j] = uint16(n % radix)
n /= radix
}
}
// if xx is not zero, then the input x must too big
if xx.Sign() != 0 {
panic("x should less then radix^m")
}
}
}