init QMCv2: RC4 (512 Byte Key)
parent
f14c0cd9c2
commit
1552a667f6
@ -0,0 +1,111 @@
|
||||
package qmc
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"math"
|
||||
|
||||
"golang.org/x/crypto/tea"
|
||||
)
|
||||
|
||||
func simpleMakeKey(salt byte, length int) []byte {
|
||||
keyBuf := make([]byte, length)
|
||||
for i := 0; i < length; i++ {
|
||||
tmp := math.Tan(float64(salt) + float64(i)*0.1)
|
||||
keyBuf[i] = byte(math.Abs(tmp) * 100.0)
|
||||
}
|
||||
return keyBuf
|
||||
}
|
||||
func DecryptKey(rawKey []byte) ([]byte, error) {
|
||||
rawKeyDec := make([]byte, base64.StdEncoding.DecodedLen(len(rawKey)))
|
||||
_, err := base64.StdEncoding.Decode(rawKeyDec, rawKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
simpleKey := simpleMakeKey(106, 8)
|
||||
teaKey := make([]byte, 16)
|
||||
for i := 0; i < 8; i++ {
|
||||
teaKey[i<<1] = simpleKey[i]
|
||||
teaKey[i<<1+1] = rawKeyDec[i]
|
||||
}
|
||||
|
||||
rs, err := decryptTencentTea(rawKeyDec[8:], teaKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append(rawKeyDec[:8], rs...), nil
|
||||
}
|
||||
func decryptTencentTea(inBuf []byte, key []byte) ([]byte, error) {
|
||||
const saltLen = 2
|
||||
const zeroLen = 7
|
||||
if len(inBuf)%8 != 0 {
|
||||
return nil, errors.New("inBuf size not a multiple of the block size")
|
||||
}
|
||||
if len(inBuf) < 16 {
|
||||
return nil, errors.New("inBuf size too small")
|
||||
}
|
||||
|
||||
blk, err := tea.NewCipherWithRounds(key, 32)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
destBuf := make([]byte, 8)
|
||||
blk.Decrypt(destBuf, inBuf)
|
||||
padLen := int(destBuf[0] & 0x7)
|
||||
outLen := len(inBuf) - 1 - padLen - saltLen - zeroLen
|
||||
if padLen+saltLen != 8 {
|
||||
return nil, errors.New("invalid pad len")
|
||||
}
|
||||
out := make([]byte, outLen)
|
||||
|
||||
ivPrev := make([]byte, 8)
|
||||
ivCur := inBuf[:8]
|
||||
|
||||
inBufPos := 8
|
||||
|
||||
destIdx := 1 + padLen
|
||||
cryptBlock := func() {
|
||||
ivPrev = ivCur
|
||||
ivCur = inBuf[inBufPos : inBufPos+8]
|
||||
|
||||
xor8Bytes(destBuf, destBuf, inBuf[inBufPos:inBufPos+8])
|
||||
blk.Decrypt(destBuf, destBuf)
|
||||
|
||||
inBufPos += 8
|
||||
destIdx = 0
|
||||
}
|
||||
for i := 1; i <= saltLen; {
|
||||
if destIdx < 8 {
|
||||
destIdx++
|
||||
i++
|
||||
} else if destIdx == 8 {
|
||||
cryptBlock()
|
||||
}
|
||||
}
|
||||
|
||||
outPos := 0
|
||||
for outPos < outLen {
|
||||
if destIdx < 8 {
|
||||
out[outPos] = destBuf[destIdx] ^ ivPrev[destIdx]
|
||||
destIdx++
|
||||
outPos++
|
||||
} else if destIdx == 8 {
|
||||
cryptBlock()
|
||||
}
|
||||
}
|
||||
|
||||
for i := 1; i <= zeroLen; i++ {
|
||||
if destBuf[destIdx] != ivPrev[destIdx] {
|
||||
return nil, errors.New("zero check failed")
|
||||
}
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
func xor8Bytes(dst, a, b []byte) {
|
||||
for i := 0; i < 8; i++ {
|
||||
dst[i] = a[i] ^ b[i]
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
package qmc
|
||||
|
||||
import (
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSimpleMakeKey(t *testing.T) {
|
||||
expect := []byte{0x69, 0x56, 0x46, 0x38, 0x2b, 0x20, 0x15, 0x0b}
|
||||
t.Run("106,8", func(t *testing.T) {
|
||||
if got := simpleMakeKey(106, 8); !reflect.DeepEqual(got, expect) {
|
||||
t.Errorf("simpleMakeKey() = %v, want %v", got, expect)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestDecryptKey(t *testing.T) {
|
||||
rc4Raw, err := os.ReadFile("./testdata/rc4_key_raw.bin")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
rc4Dec, err := os.ReadFile("./testdata/rc4_key.bin")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
rawKey []byte
|
||||
want []byte
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
"512",
|
||||
rc4Raw,
|
||||
rc4Dec,
|
||||
false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := DecryptKey(tt.rawKey)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("DecryptKey() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("DecryptKey() got = %v..., want %v...", string(got[:32]), string(tt.want[:32]))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -0,0 +1,125 @@
|
||||
package qmc
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Mflac0Decoder struct {
|
||||
r io.ReadSeeker
|
||||
|
||||
audioLen int
|
||||
decodedKey []byte
|
||||
rc4 *rc4Cipher
|
||||
offset int
|
||||
|
||||
rawMetaExtra1 int
|
||||
rawMetaExtra2 int
|
||||
}
|
||||
|
||||
func (d *Mflac0Decoder) Read(p []byte) (int, error) {
|
||||
n := len(p)
|
||||
if d.audioLen-d.offset <= 0 {
|
||||
return 0, io.EOF
|
||||
} else if d.audioLen-d.offset < n {
|
||||
n = d.audioLen - d.offset
|
||||
}
|
||||
m, err := d.r.Read(p[:n])
|
||||
if m == 0 {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
d.rc4.Process(p[:m], d.offset)
|
||||
d.offset += m
|
||||
return m, err
|
||||
|
||||
}
|
||||
|
||||
func NewMflac0Decoder(r io.ReadSeeker) (*Mflac0Decoder, error) {
|
||||
d := &Mflac0Decoder{r: r}
|
||||
if err := d.searchKey(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(d.decodedKey) > 300 {
|
||||
var err error
|
||||
d.rc4, err = NewRC4Cipher(d.decodedKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
panic("not implement") //todo: impl
|
||||
}
|
||||
|
||||
_, err := d.r.Seek(0, io.SeekStart)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func (d *Mflac0Decoder) searchKey() error {
|
||||
if _, err := d.r.Seek(-4, io.SeekEnd); err != nil {
|
||||
return err
|
||||
}
|
||||
buf, err := io.ReadAll(io.LimitReader(d.r, 4))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if string(buf) == "QTag" {
|
||||
if err := d.readRawMetaQTag(); err != nil {
|
||||
return err
|
||||
}
|
||||
} // todo: ...
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Mflac0Decoder) readRawMetaQTag() error {
|
||||
// get raw meta data len
|
||||
if _, err := d.r.Seek(-8, io.SeekEnd); err != nil {
|
||||
return err
|
||||
}
|
||||
buf, err := io.ReadAll(io.LimitReader(d.r, 4))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rawMetaLen := int64(binary.BigEndian.Uint32(buf))
|
||||
|
||||
// read raw meta data
|
||||
audioLen, err := d.r.Seek(-(8 + rawMetaLen), io.SeekEnd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.audioLen = int(audioLen)
|
||||
rawMetaData, err := io.ReadAll(io.LimitReader(d.r, rawMetaLen))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
items := strings.Split(string(rawMetaData), ",")
|
||||
if len(items) != 3 {
|
||||
return errors.New("invalid raw meta data")
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
d.decodedKey, err = DecryptKey([]byte(items[0]))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
d.rawMetaExtra1, err = strconv.Atoi(items[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.rawMetaExtra2, err = strconv.Atoi(items[2])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package qmc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func loadTestDataRC4Mflac0() ([]byte, []byte, error) {
|
||||
encBody, err := os.ReadFile("./testdata/rc4_raw.bin")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
encSuffix, err := os.ReadFile("./testdata/rc4_suffix_mflac0.bin")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
target, err := os.ReadFile("./testdata/rc4_target.bin")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return bytes.Join([][]byte{encBody, encSuffix}, nil), target, nil
|
||||
|
||||
}
|
||||
func TestMflac0Decoder_Read(t *testing.T) {
|
||||
raw, target, err := loadTestDataRC4Mflac0()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Run("mflac0-file", func(t *testing.T) {
|
||||
d, err := NewMflac0Decoder(bytes.NewReader(raw))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
buf := make([]byte, len(target))
|
||||
if _, err := io.ReadFull(d, buf); err != nil {
|
||||
t.Errorf("read bytes from decoder error = %v", err)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(buf, target) {
|
||||
t.Errorf("Process() got = %v, want %v", buf[:32], target[:32])
|
||||
}
|
||||
})
|
||||
}
|
@ -0,0 +1,133 @@
|
||||
package qmc
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// A rc4Cipher is an instance of RC4 using a particular key.
|
||||
type rc4Cipher struct {
|
||||
box []byte
|
||||
key []byte
|
||||
hash uint32
|
||||
boxTmp []byte
|
||||
}
|
||||
|
||||
// NewRC4Cipher creates and returns a new rc4Cipher. The key argument should be the
|
||||
// RC4 key, at least 1 byte and at most 256 bytes.
|
||||
func NewRC4Cipher(key []byte) (*rc4Cipher, error) {
|
||||
n := len(key)
|
||||
if n == 0 {
|
||||
return nil, errors.New("crypto/rc4: invalid key size")
|
||||
}
|
||||
|
||||
var c = rc4Cipher{key: key}
|
||||
c.box = make([]byte, n)
|
||||
c.boxTmp = make([]byte, n)
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
c.box[i] = byte(i)
|
||||
}
|
||||
|
||||
var j = 0
|
||||
for i := 0; i < n; i++ {
|
||||
j = (j + int(c.box[i]) + int(key[i%n])) % n
|
||||
c.box[i], c.box[j] = c.box[j], c.box[i]
|
||||
}
|
||||
c.getHashBase()
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
func (c *rc4Cipher) getHashBase() {
|
||||
c.hash = 1
|
||||
for i := 0; i < len(c.key); i++ {
|
||||
v := uint32(c.key[i])
|
||||
if v == 0 {
|
||||
continue
|
||||
}
|
||||
nextHash := c.hash * v
|
||||
if nextHash == 0 || nextHash <= c.hash {
|
||||
break
|
||||
}
|
||||
c.hash = nextHash
|
||||
}
|
||||
}
|
||||
|
||||
const rc4SegmentSize = 5120
|
||||
|
||||
func (c *rc4Cipher) Process(src []byte, offset int) {
|
||||
toProcess := len(src)
|
||||
processed := 0
|
||||
markProcess := func(p int) (finished bool) {
|
||||
offset += p
|
||||
toProcess -= p
|
||||
processed += p
|
||||
return toProcess == 0
|
||||
}
|
||||
|
||||
if offset < 128 {
|
||||
blockSize := toProcess
|
||||
if blockSize > 128-offset {
|
||||
blockSize = 128 - offset
|
||||
}
|
||||
c.encFirstSegment(src[:blockSize], offset)
|
||||
if markProcess(blockSize) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if offset%rc4SegmentSize != 0 {
|
||||
blockSize := toProcess
|
||||
if blockSize > rc4SegmentSize-offset%rc4SegmentSize {
|
||||
blockSize = rc4SegmentSize - offset%rc4SegmentSize
|
||||
}
|
||||
k := src[processed : processed+blockSize]
|
||||
c.encASegment(k, offset)
|
||||
if markProcess(blockSize) {
|
||||
return
|
||||
}
|
||||
}
|
||||
for toProcess > rc4SegmentSize {
|
||||
c.encASegment(src[processed:processed+rc4SegmentSize], offset)
|
||||
markProcess(rc4SegmentSize)
|
||||
}
|
||||
|
||||
if toProcess > 0 {
|
||||
c.encASegment(src[processed:], offset)
|
||||
}
|
||||
}
|
||||
func (c *rc4Cipher) encFirstSegment(buf []byte, offset int) {
|
||||
n := len(c.box)
|
||||
for i := 0; i < len(buf); i++ {
|
||||
idx1 := offset + i
|
||||
segmentID := int(c.key[idx1%n])
|
||||
idx2 := int(float64(c.hash) / float64((idx1+1)*segmentID) * 100.0)
|
||||
buf[i] ^= c.key[idx2%n]
|
||||
}
|
||||
}
|
||||
|
||||
func (c *rc4Cipher) encASegment(buf []byte, offset int) {
|
||||
n := len(c.box)
|
||||
copy(c.boxTmp, c.box)
|
||||
|
||||
segmentID := (offset / rc4SegmentSize) & 0x1FF
|
||||
|
||||
if n <= segmentID {
|
||||
return
|
||||
}
|
||||
|
||||
idx2 := int64(float64(c.hash) /
|
||||
float64((offset/rc4SegmentSize+1)*int(c.key[segmentID])) *
|
||||
100.0)
|
||||
skipLen := int((idx2 & 0x1FF) + int64(offset%rc4SegmentSize))
|
||||
|
||||
j, k := 0, 0
|
||||
|
||||
for i := -skipLen; i < len(buf); i++ {
|
||||
j = (j + 1) % n
|
||||
k = (int(c.boxTmp[j]) + k) % n
|
||||
c.boxTmp[j], c.boxTmp[k] = c.boxTmp[k], c.boxTmp[j]
|
||||
if i >= 0 {
|
||||
buf[i] ^= c.boxTmp[int(c.boxTmp[j])+int(c.boxTmp[k])%n]
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
package qmc
|
||||
|
||||
import (
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func loadTestData() (*rc4Cipher, []byte, []byte, error) {
|
||||
key, err := os.ReadFile("./testdata/rc4_key.bin")
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
raw, err := os.ReadFile("./testdata/rc4_raw.bin")
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
target, err := os.ReadFile("./testdata/rc4_target.bin")
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
c, err := NewRC4Cipher(key)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
return c, raw, target, nil
|
||||
}
|
||||
func Test_rc4Cipher_Process(t *testing.T) {
|
||||
c, raw, target, err := loadTestData()
|
||||
if err != nil {
|
||||
t.Errorf("load testing data failed: %s", err)
|
||||
}
|
||||
t.Run("overall", func(t *testing.T) {
|
||||
c.Process(raw, 0)
|
||||
if !reflect.DeepEqual(raw, target) {
|
||||
t.Error("overall")
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func Test_rc4Cipher_encFirstSegment(t *testing.T) {
|
||||
c, raw, target, err := loadTestData()
|
||||
if err != nil {
|
||||
t.Errorf("load testing data failed: %s", err)
|
||||
}
|
||||
t.Run("first-block(0~128)", func(t *testing.T) {
|
||||
c.Process(raw[:128], 0)
|
||||
if !reflect.DeepEqual(raw[:128], target[:128]) {
|
||||
t.Error("first-block(0~128)")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Test_rc4Cipher_encASegment(t *testing.T) {
|
||||
c, raw, target, err := loadTestData()
|
||||
if err != nil {
|
||||
t.Errorf("load testing data failed: %s", err)
|
||||
}
|
||||
t.Run("align-block(128~5120)", func(t *testing.T) {
|
||||
c.Process(raw[128:5120], 128)
|
||||
if !reflect.DeepEqual(raw[128:5120], target[128:5120]) {
|
||||
t.Error("align-block(128~5120)")
|
||||
}
|
||||
})
|
||||
t.Run("simple-block(5120~10240)", func(t *testing.T) {
|
||||
c.Process(raw[5120:10240], 5120)
|
||||
if !reflect.DeepEqual(raw[5120:10240], target[5120:10240]) {
|
||||
t.Error("align-block(128~5120)")
|
||||
}
|
||||
})
|
||||
}
|
@ -0,0 +1 @@
|
||||
dRzX3p5ZYqAlp7lLSs9Zr0rw1iEZy23bB670x4ch2w97x14Zwpk1UXbKU4C2sOS7uZ0NB5QM7ve9GnSrr2JHxP74hVNONwVV77CdOOVb807317KvtI5Yd6h08d0c5W88rdV46C235YGDjUSZj5314YTzy0b6vgh4102P7E273r911Nl464XV83Hr00rkAHkk791iMGSJH95GztN28u2Nv5s9Xx38V69o4a8aIXxbx0g1EM0623OEtbtO9zsqCJfj6MhU7T8iVS6M3q19xhq6707E6r7wzPO6Yp4BwBmgg4F95Lfl0vyF7YO6699tb5LMnr7iFx29o98hoh3O3Rd8h9Juu8P1wG7vdnO5YtRlykhUluYQblNn7XwjBJ53HAyKVraWN5dG7pv7OMl1s0RykPh0p23qfYzAAMkZ1M422pEd07TA9OCKD1iybYxWH06xj6A8mzmcnYGT9P1a5Ytg2EF5LG3IknL2r3AUz99Y751au6Cr401mfAWK68WyEBe5
|
@ -0,0 +1 @@
|
||||
ZFJ6WDNwNVrjEJZB1o6QjkQV2ZbHSw/2Eb00q1+4z9SVWYyFWO1PcSQrJ5326ubLklmk2ab3AEyIKNUu8DFoAoAc9dpzpTmc+pdkBHjM/bW2jWx+dCyC8vMTHE+DHwaK14UEEGW47ZXMDi7PRCQ2Jpm/oXVdHTIlyrc+bRmKfMith0L2lFQ+nW8CCjV6ao5ydwkZhhNOmRdrCDcUXSJH9PveYwra9/wAmGKWSs9nemuMWKnbjp1PkcxNQexicirVTlLX7PVgRyFyzNyUXgu+R2S4WTmLwjd8UsOyW/dc2mEoYt+vY2lq1X4hFBtcQGOAZDeC+mxrN0EcW8tjS6P4TjOjiOKNMxIfMGSWkSKL3H7z5K7nR1AThW20H2bP/LcpsdaL0uZ/js1wFGpdIfFx9rnLC78itL0WwDleIqp9TBMX/NwakGgIPIbjBwfgyD8d8XKYuLEscIH0ZGdjsadB5XjybgdE3ppfeFEcQiqpnodlTaQRm3KDIF9ATClP0mTl8XlsSojsZ468xseS1Ib2iinx/0SkK3UtJDwp8DH3/+ELisgXd69Bf0pve7wbrQzzMUs9/Ogvvo6ULsIkQfApJ8cSegDYklzGXiLNH7hZYnXDLLSNejD7NvQouULSmGsBbGzhZ5If0NP/6AhSbpzqWLDlabTDgeWWnFeZpBnlK6SMxo+YFFk1Y0XLKsd69+jj
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue