Skip to content

Instantly share code, notes, and snippets.

@emmansun
Last active June 14, 2024 09:51
Show Gist options
  • Save emmansun/29c319b30baa36eda0bf36b3d10169b0 to your computer and use it in GitHub Desktop.
Save emmansun/29c319b30baa36eda0bf36b3d10169b0 to your computer and use it in GitHub Desktop.
PBESHAAndTwofish-CBC 的GO实现以及相应JAVA实现
package main
import (
"bytes"
"crypto/cipher"
"crypto/sha1"
"errors"
"fmt"
"math/big"
"unicode/utf16"
"github.com/emmansun/gmsm/padding"
"golang.org/x/crypto/twofish"
)
var (
one = big.NewInt(1)
)
// sha1Sum returns the SHA-1 hash of in.
func sha1Sum(in []byte) []byte {
sum := sha1.Sum(in)
return sum[:]
}
// fillWithRepeats returns v*ceiling(len(pattern) / v) bytes consisting of
// repeats of pattern.
func fillWithRepeats(pattern []byte, v int) []byte {
if len(pattern) == 0 {
return nil
}
outputLen := v * ((len(pattern) + v - 1) / v)
return bytes.Repeat(pattern, (outputLen+len(pattern)-1)/len(pattern))[:outputLen]
}
func pbkdf(hash func([]byte) []byte, u, v int, salt, password []byte, r int, ID byte, size int) (key []byte) {
// implementation of https://tools.ietf.org/html/rfc7292#appendix-B.2 , RFC text verbatim in comments
// Let H be a hash function built around a compression function f:
// Z_2^u x Z_2^v -> Z_2^u
// (that is, H has a chaining variable and output of length u bits, and
// the message input to the compression function of H is v bits). The
// values for u and v are as follows:
// HASH FUNCTION VALUE u VALUE v
// MD2, MD5 128 512
// SHA-1 160 512
// SHA-224 224 512
// SHA-256 256 512
// SHA-384 384 1024
// SHA-512 512 1024
// SHA-512/224 224 1024
// SHA-512/256 256 1024
// Furthermore, let r be the iteration count.
// We assume here that u and v are both multiples of 8, as are the
// lengths of the password and salt strings (which we denote by p and s,
// respectively) and the number n of pseudorandom bits required. In
// addition, u and v are of course non-zero.
// For information on security considerations for MD5 [19], see [25] and
// [1], and on those for MD2, see [18].
// The following procedure can be used to produce pseudorandom bits for
// a particular "purpose" that is identified by a byte called "ID".
// This standard specifies 3 different values for the ID byte:
// 1. If ID=1, then the pseudorandom bits being produced are to be used
// as key material for performing encryption or decryption.
// 2. If ID=2, then the pseudorandom bits being produced are to be used
// as an IV (Initial Value) for encryption or decryption.
// 3. If ID=3, then the pseudorandom bits being produced are to be used
// as an integrity key for MACing.
// 1. Construct a string, D (the "diversifier"), by concatenating v/8
// copies of ID.
var D []byte
for i := 0; i < v; i++ {
D = append(D, ID)
}
// 2. Concatenate copies of the salt together to create a string S of
// length v(ceiling(s/v)) bits (the final copy of the salt may be
// truncated to create S). Note that if the salt is the empty
// string, then so is S.
S := fillWithRepeats(salt, v)
// 3. Concatenate copies of the password together to create a string P
// of length v(ceiling(p/v)) bits (the final copy of the password
// may be truncated to create P). Note that if the password is the
// empty string, then so is P.
P := fillWithRepeats(password, v)
// 4. Set I=S||P to be the concatenation of S and P.
I := append(S, P...)
// 5. Set c=ceiling(n/u).
c := (size + u - 1) / u
// 6. For i=1, 2, ..., c, do the following:
A := make([]byte, c*20)
var IjBuf []byte
for i := 0; i < c; i++ {
// A. Set A2=H^r(D||I). (i.e., the r-th hash of D||1,
// H(H(H(... H(D||I))))
Ai := hash(append(D, I...))
for j := 1; j < r; j++ {
Ai = hash(Ai)
}
copy(A[i*20:], Ai[:])
if i < c-1 { // skip on last iteration
// B. Concatenate copies of Ai to create a string B of length v
// bits (the final copy of Ai may be truncated to create B).
var B []byte
for len(B) < v {
B = append(B, Ai[:]...)
}
B = B[:v]
// C. Treating I as a concatenation I_0, I_1, ..., I_(k-1) of v-bit
// blocks, where k=ceiling(s/v)+ceiling(p/v), modify I by
// setting I_j=(I_j+B+1) mod 2^v for each j.
{
Bbi := new(big.Int).SetBytes(B)
Ij := new(big.Int)
for j := 0; j < len(I)/v; j++ {
Ij.SetBytes(I[j*v : (j+1)*v])
Ij.Add(Ij, Bbi)
Ij.Add(Ij, one)
Ijb := Ij.Bytes()
// We expect Ijb to be exactly v bytes,
// if it is longer or shorter we must
// adjust it accordingly.
if len(Ijb) > v {
Ijb = Ijb[len(Ijb)-v:]
}
if len(Ijb) < v {
if IjBuf == nil {
IjBuf = make([]byte, v)
}
bytesShort := v - len(Ijb)
for i := 0; i < bytesShort; i++ {
IjBuf[i] = 0
}
copy(IjBuf[bytesShort:], Ijb)
Ijb = IjBuf
}
copy(I[j*v:(j+1)*v], Ijb)
}
}
}
}
// 7. Concatenate A_1, A_2, ..., A_c together to form a pseudorandom
// bit string, A.
// 8. Use the first n bits of A as the output of this entire process.
return A[:size]
// If the above process is being used to generate a DES key, the process
// should be used to create 64 random bits, and the key's parity bits
// should be set after the 64 bits have been produced. Similar concerns
// hold for 2-key and 3-key triple-DES keys, for CDMF keys, and for any
// similar keys with parity bits "built into them".
}
// bmpStringZeroTerminated returns s encoded in UCS-2 with a zero terminator.
func bmpStringZeroTerminated(s string) ([]byte, error) {
// References:
// https://tools.ietf.org/html/rfc7292#appendix-B.1
// The above RFC provides the info that BMPStrings are NULL terminated.
ret, err := bmpString(s)
if err != nil {
return nil, err
}
return append(ret, 0, 0), nil
}
// bmpString returns s encoded in UCS-2
func bmpString(s string) ([]byte, error) {
// References:
// https://tools.ietf.org/html/rfc7292#appendix-B.1
// https://en.wikipedia.org/wiki/Plane_(Unicode)#Basic_Multilingual_Plane
// - non-BMP characters are encoded in UTF 16 by using a surrogate pair of 16-bit codes
// EncodeRune returns 0xfffd if the rune does not need special encoding
ret := make([]byte, 0, 2*len(s)+2)
for _, r := range s {
if t, _ := utf16.EncodeRune(r); t != 0xfffd {
return nil, errors.New("string contains characters that cannot be encoded in UCS-2")
}
ret = append(ret, byte(r/256), byte(r%256))
}
return ret, nil
}
func PBEWithSAH1AndTwoFishCBCEncrypt(password string, salt []byte, iterations int, plaintext []byte) ([]byte, error) {
encodedPassword, err := bmpStringZeroTerminated(password)
if err != nil {
return nil, err
}
key := pbkdf(sha1Sum, 20, 64, salt, encodedPassword, iterations, 1, 32) // Use 32 bytes key size for TwoFish
iv := pbkdf(sha1Sum, 20, 64, salt, encodedPassword, iterations, 2, twofish.BlockSize)
block, err := twofish.NewCipher(key)
if err != nil {
return nil, err
}
pkcs7 := padding.NewPKCS7Padding(twofish.BlockSize)
plaintext = pkcs7.Pad(plaintext)
encrypter := cipher.NewCBCEncrypter(block, iv)
ciphertext := make([]byte, len(plaintext))
encrypter.CryptBlocks(ciphertext, plaintext)
return ciphertext, nil
}
func PBEWithSAH1AndTwoFishCBCDecrypt(password string, salt []byte, iterations int, ciphertext []byte) ([]byte, error) {
// Check if the ciphertext is a multiple of the block size
// We need to return an error if it is not a multiple of the block size
if len(ciphertext)%twofish.BlockSize != 0 {
return nil, errors.New("input is not a multiple of the block size")
}
encodedPassword, err := bmpStringZeroTerminated(password)
if err != nil {
return nil, err
}
key := pbkdf(sha1Sum, 20, 64, salt, encodedPassword, iterations, 1, 32) // Use 32 bytes key size for TwoFish
iv := pbkdf(sha1Sum, 20, 64, salt, encodedPassword, iterations, 2, twofish.BlockSize)
block, err := twofish.NewCipher(key)
if err != nil {
return nil, err
}
decrypter := cipher.NewCBCDecrypter(block, iv)
plaintext := make([]byte, len(ciphertext))
decrypter.CryptBlocks(plaintext, ciphertext)
pkcs7 := padding.NewPKCS7Padding(twofish.BlockSize)
return pkcs7.Unpad(plaintext)
}
func main() {
password := "password"
salt := []byte("salt")
plaintext := []byte("Hello CFCA SADK!")
iterations := 1000
ciphertext, err := PBEWithSAH1AndTwoFishCBCEncrypt(password, salt, iterations, plaintext)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("Encrypted: %x\n", ciphertext)
decrypted, err := PBEWithSAH1AndTwoFishCBCDecrypt(password, salt, iterations, ciphertext)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("Decrypted: %s\n", decrypted)
}
package person.emmansun.crypto.pbe;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Hex;
import javax.crypto.Cipher;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
import java.security.Security;
public class TwofishExample {
private static final String password = "password";
private static final String salt = "salt";
public static void main(String[] args) throws Exception {
Security.addProvider(new BouncyCastleProvider());
PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray());
SecretKeyFactory skf = SecretKeyFactory.getInstance("PBEWithSHAAndTwofish-CBC", "BC");
PBEParameterSpec pbeParameterSpec = new PBEParameterSpec(salt.getBytes(), 1000);
Cipher cipher = Cipher.getInstance("PBEWithSHAAndTwofish-CBC", "BC");
cipher.init(Cipher.ENCRYPT_MODE, skf.generateSecret(keySpec), pbeParameterSpec);
byte[] cipherText = cipher.doFinal("Hello CFCA SADK!".getBytes());
System.out.println("CipherText=" + Hex.toHexString(cipherText));
}
}
@emmansun
Copy link
Author

emmansun commented Jun 14, 2024

GO语言的实现参考自pkcs12项目。这都是些古早且不推荐继续使用的算法。。。

@Nightlan
Copy link

好的,感谢大神,确实解开了

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment