mirror of https://github.com/tidwall/tile38.git
129 lines
3.5 KiB
Go
129 lines
3.5 KiB
Go
|
package rfc3961
|
||
|
|
||
|
/*
|
||
|
Implementation of the n-fold algorithm as defined in RFC 3961.
|
||
|
|
||
|
n-fold is an algorithm that takes m input bits and "stretches" them
|
||
|
to form n output bits with equal contribution from each input bit to
|
||
|
the output, as described in [Blumenthal96]:
|
||
|
|
||
|
We first define a primitive called n-folding, which takes a
|
||
|
variable-length input block and produces a fixed-length output
|
||
|
sequence. The intent is to give each input bit approximately
|
||
|
equal weight in determining the value of each output bit. Note
|
||
|
that whenever we need to treat a string of octets as a number, the
|
||
|
assumed representation is Big-Endian -- Most Significant Byte
|
||
|
first.
|
||
|
|
||
|
To n-fold a number X, replicate the input value to a length that
|
||
|
is the least common multiple of n and the length of X. Before
|
||
|
each repetition, the input is rotated to the right by 13 bit
|
||
|
positions. The successive n-bit chunks are added together using
|
||
|
1's-complement addition (that is, with end-around carry) to yield
|
||
|
a n-bit result....
|
||
|
*/
|
||
|
|
||
|
/* Credits
|
||
|
This golang implementation of nfold used the following project for help with implementation detail.
|
||
|
Although their source is in java it was helpful as a reference implementation of the RFC.
|
||
|
You can find the source code of their open source project along with license information below.
|
||
|
We acknowledge and are grateful to these developers for their contributions to open source
|
||
|
|
||
|
Project: Apache Directory (http://http://directory.apache.org/)
|
||
|
https://svn.apache.org/repos/asf/directory/apacheds/tags/1.5.1/kerberos-shared/src/main/java/org/apache/directory/server/kerberos/shared/crypto/encryption/NFold.java
|
||
|
License: http://www.apache.org/licenses/LICENSE-2.0
|
||
|
*/
|
||
|
|
||
|
// Nfold expands the key to ensure it is not smaller than one cipher block.
|
||
|
// Defined in RFC 3961.
|
||
|
//
|
||
|
// m input bytes that will be "stretched" to the least common multiple of n bits and the bit length of m.
|
||
|
func Nfold(m []byte, n int) []byte {
|
||
|
k := len(m) * 8
|
||
|
|
||
|
//Get the lowest common multiple of the two bit sizes
|
||
|
lcm := lcm(n, k)
|
||
|
relicate := lcm / k
|
||
|
var sumBytes []byte
|
||
|
|
||
|
for i := 0; i < relicate; i++ {
|
||
|
rotation := 13 * i
|
||
|
sumBytes = append(sumBytes, rotateRight(m, rotation)...)
|
||
|
}
|
||
|
|
||
|
nfold := make([]byte, n/8)
|
||
|
sum := make([]byte, n/8)
|
||
|
for i := 0; i < lcm/n; i++ {
|
||
|
for j := 0; j < n/8; j++ {
|
||
|
sum[j] = sumBytes[j+(i*len(sum))]
|
||
|
}
|
||
|
nfold = onesComplementAddition(nfold, sum)
|
||
|
}
|
||
|
return nfold
|
||
|
}
|
||
|
|
||
|
func onesComplementAddition(n1, n2 []byte) []byte {
|
||
|
numBits := len(n1) * 8
|
||
|
out := make([]byte, numBits/8)
|
||
|
carry := 0
|
||
|
for i := numBits - 1; i > -1; i-- {
|
||
|
n1b := getBit(&n1, i)
|
||
|
n2b := getBit(&n2, i)
|
||
|
s := n1b + n2b + carry
|
||
|
|
||
|
if s == 0 || s == 1 {
|
||
|
setBit(&out, i, s)
|
||
|
carry = 0
|
||
|
} else if s == 2 {
|
||
|
carry = 1
|
||
|
} else if s == 3 {
|
||
|
setBit(&out, i, 1)
|
||
|
carry = 1
|
||
|
}
|
||
|
}
|
||
|
if carry == 1 {
|
||
|
carryArray := make([]byte, len(n1))
|
||
|
carryArray[len(carryArray)-1] = 1
|
||
|
out = onesComplementAddition(out, carryArray)
|
||
|
}
|
||
|
return out
|
||
|
}
|
||
|
|
||
|
func rotateRight(b []byte, step int) []byte {
|
||
|
out := make([]byte, len(b))
|
||
|
bitLen := len(b) * 8
|
||
|
for i := 0; i < bitLen; i++ {
|
||
|
v := getBit(&b, i)
|
||
|
setBit(&out, (i+step)%bitLen, v)
|
||
|
}
|
||
|
return out
|
||
|
}
|
||
|
|
||
|
func lcm(x, y int) int {
|
||
|
return (x * y) / gcd(x, y)
|
||
|
}
|
||
|
|
||
|
func gcd(x, y int) int {
|
||
|
for y != 0 {
|
||
|
x, y = y, x%y
|
||
|
}
|
||
|
return x
|
||
|
}
|
||
|
|
||
|
func getBit(b *[]byte, p int) int {
|
||
|
pByte := p / 8
|
||
|
pBit := uint(p % 8)
|
||
|
vByte := (*b)[pByte]
|
||
|
vInt := int(vByte >> (8 - (pBit + 1)) & 0x0001)
|
||
|
return vInt
|
||
|
}
|
||
|
|
||
|
func setBit(b *[]byte, p, v int) {
|
||
|
pByte := p / 8
|
||
|
pBit := uint(p % 8)
|
||
|
oldByte := (*b)[pByte]
|
||
|
var newByte byte
|
||
|
newByte = byte(v<<(8-(pBit+1))) | oldByte
|
||
|
(*b)[pByte] = newByte
|
||
|
}
|