Charles ライセンスキー生成メカニズムの解析

基本的なアクティベーション方法

Charlesでアクティベーションを行うには、メニューから Help -> Register Charles を選択します。

表示されたダイアログの Registered Name に任意の名前を、生成された License key に入力して Register をクリックします。

以下に、このライセンスキーを計算するためのJavaおよびGoによる実装例を示します。

Java による実装

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Random;
import java.util.Scanner;

public class CharlesLicenseGenerator {

    private static final int ROUNDS = 12;
    private static final int ROUND_KEYS = 2 * (ROUNDS + 1);
    private static final Random RANDOM = new Random();

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String userName = scanner.nextLine().toLowerCase();
        RANDOM.setSeed(System.nanoTime());
        String licenseKey = generateKey(userName);
        System.out.println("Registered Name: " + userName + "    License Key: " + licenseKey);
    }

    private static String generateKey(String inputText) {
        byte[] nameBytes = inputText.getBytes();
        int nameLength = nameBytes.length + 4;
        int paddedLength = ((-nameLength) & (8 - 1)) + nameLength;
        ByteBuffer inputBuffer = ByteBuffer.allocate(paddedLength);
        inputBuffer.order(ByteOrder.BIG_ENDIAN);
        inputBuffer.putInt(nameBytes.length);
        inputBuffer.put(nameBytes);

        long cipherKey1 = 0x7a21c951691cd470L;
        long cipherKey2 = -5408575981733630035L;
        CkCipher cipher = new CkCipher(cipherKey1);
        ByteBuffer outputBuffer = ByteBuffer.allocate(paddedLength);
        outputBuffer.order(ByteOrder.BIG_ENDIAN);

        for (int i = 0; i < paddedLength; i += 8) {
            long currentBlock = inputBuffer.getLong(i);
            long encryptedBlock = cipher.encrypt(currentBlock);
            outputBuffer.putLong(encryptedBlock);
        }

        int checksum = 0;
        for (byte b : outputBuffer.array()) {
            checksum = rotateLeft(checksum ^ (int) b, 3);
        }
        int prefix = checksum ^ 0x54882f8a;
        int suffix = getSuffix();
        long combinedValue = ((long) prefix << 32) | (suffix & 0xffffffffL);

        long decryptedValue = new CkCipher(cipherKey2).decrypt(combinedValue);
        long mask = 0;
        for (int i = 56; i >= 0; i -= 8) {
            mask ^= (combinedValue >> i) & 0xff;
        }

        int verificationByte = (int) (mask & 0xff);
        if (verificationByte < 0) {
            verificationByte = -verificationByte;
        }
        return String.format("%02x%016x", verificationByte, decryptedValue);
    }

    private static int getSuffix() {
        int randomSuffix = RANDOM.nextInt();
        int suffixType = (randomSuffix >> 16);
        if (suffixType == 0x0401 || suffixType == 0x0402 || suffixType == 0x0403) {
            return randomSuffix;
        } else {
            return (randomSuffix & 0xffffff) | 0x01000000;
        }
    }

    private static class CkCipher {
        private int[] roundKeys = new int[ROUND_KEYS];

        public CkCipher(long cipherKey) {
            int[] keyData = new int[]{(int) cipherKey, (int) (cipherKey >>> 32)};
            roundKeys[0] = -1209970333;
            for (int i = 1; i < ROUND_KEYS; i++) {
                roundKeys[i] = roundKeys[i - 1] - 1640531527;
            }
            int a = 0, b = 0, keyIndex = 0, dataIndex = 0;
            for (int k = 0; k < 3 * ROUND_KEYS; k++) {
                roundKeys[keyIndex] = rotateLeft(roundKeys[keyIndex] + (a + b), 3);
                a = roundKeys[keyIndex];
                keyData[dataIndex] = rotateLeft(keyData[dataIndex] + (a + b), a + b);
                b = keyData[dataIndex];
                keyIndex = (keyIndex + 1) % ROUND_KEYS;
                dataIndex = (dataIndex + 1) % 2;
            }
        }

        public long encrypt(long input) {
            int a = (int) input + roundKeys[0];
            int b = (int) (input >>> 32) + roundKeys[1];
            for (int r = 1; r <= ROUNDS; r++) {
                a = rotateLeft(a ^ b, b) + roundKeys[2 * r];
                b = rotateLeft(b ^ a, a) + roundKeys[2 * r + 1];
            }
            return combine(a, b);
        }

        public long decrypt(long input) {
            int a = (int) input;
            int b = (int) (input >>> 32);
            for (int i = ROUNDS; i > 0; i--) {
                b = rotateRight(b - roundKeys[2 * i + 1], a) ^ a;
                a = rotateRight(a - roundKeys[2 * i], b) ^ b;
            }
            b -= roundKeys[1];
            a -= roundKeys[0];
            return combine(a, b);
        }
    }

    private static int rotateLeft(int value, int shift) {
        return (value << (shift & 31)) | (value >>> (32 - (shift & 31)));
    }

    private static int rotateRight(int value, int shift) {
        return (value >>> (shift & 31)) | (value << (32 - (shift & 31)));
    }

    private static long combine(int high, int low) {
        return ((long) high & 0xffffffffL) | ((long) low << 32);
    }
}

Go による実装

package main

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"math/rand"
	"time"
)

const (
	roundCount    = 12
	roundKeyCount = 2 * (roundCount + 1)
)

func main() {
	rand.Seed(time.Now().UnixNano())

	targetName := "Charles"

	key := computeLicenseKey(targetName)
	fmt.Println("Registered Name:", targetName, "    License Key:", key)
}

func computeLicenseKey(input string) string {
	nameBytes := []byte(input)
	payloadLen := len(nameBytes) + 4
	paddedLen := ((-payloadLen) & (8 - 1)) + payloadLen

	buf := new(bytes.Buffer)
	binary.Write(buf, binary.BigEndian, uint32(len(nameBytes)))
	buf.Write(nameBytes)

	var cipherConstant1 int64 = 0x7a21c951691cd470
	var cipherConstant2 int64 = -5408575981733630035

	cipher := newCipher(cipherConstant1)
	encryptedBuf := new(bytes.Buffer)

	for i := 0; i < paddedLen; i += 8 {
		blockBytes := buf.Bytes()[i : i+8]
		blockReader := bytes.NewReader(blockBytes)
		var block int64
		binary.Read(blockReader, binary.BigEndian, &block)

		encryptedBlock := cipher.encrypt(block)

		encryptedBuf.WriteByte(byte(encryptedBlock >> 56))
		encryptedBuf.WriteByte(byte(encryptedBlock >> 48))
		encryptedBuf.WriteByte(byte(encryptedBlock >> 40))
		encryptedBuf.WriteByte(byte(encryptedBlock >> 32))
		encryptedBuf.WriteByte(byte(encryptedBlock >> 24))
		encryptedBuf.WriteByte(byte(encryptedBlock >> 16))
		encryptedBuf.WriteByte(byte(encryptedBlock >> 8))
		encryptedBuf.WriteByte(byte(encryptedBlock))
	}

	var checksum int32
	for _, b := range encryptedBuf.Bytes() {
		checksum = rotateLeft(checksum^int32(int8(b)), 3)
	}
	prefix := checksum ^ 0x54882f8a
	suffix := generateSuffix()
	combined := int64(prefix)<<32 | int64(suffix)

	decrypted := newCipher(cipherConstant2).decrypt(combined)

	var mask int64
	for i := 56; i >= 0; i -= 8 {
		mask ^= int64((uint64(combined) >> i) & 0xff)
	}

	verification := int32(mask & 0xff)
	if verification < 0 {
		verification = -verification
	}
	return fmt.Sprintf("%02x%016x", verification, uint64(decrypted))
}

func generateSuffix() int32 {
	randomValue := rand.Int31()
	suffixType := randomValue >> 16
	switch suffixType {
	case 0x0401, 0x0402, 0x0403:
		return randomValue
	default:
		return (randomValue & 0xffffff) | 0x01000000
	}
}

type cipher struct {
	roundKeys [roundKeyCount]int32
}

func newCipher(key int64) cipher {
	c := cipher{}
	var keyParts [2]int32
	keyParts[0] = int32(key)
	keyParts[1] = int32(uint64(key) >> 32)

	c.roundKeys[0] = -1209970333
	for i := 1; i < roundKeyCount; i++ {
		c.roundKeys[i] = c.roundKeys[i-1] + -1640531527
	}
	var a, b int32
	var keyIndex, partIndex int

	for iteration := 0; iteration < 3*roundKeyCount; iteration++ {
		c.roundKeys[keyIndex] = rotateLeft(c.roundKeys[keyIndex]+(a+b), 3)
		a = c.roundKeys[keyIndex]
		keyParts[partIndex] = rotateLeft(keyParts[partIndex]+(a+b), a+b)
		b = keyParts[partIndex]
		keyIndex = (keyIndex + 1) % roundKeyCount
		partIndex = (partIndex + 1) % 2
	}
	return c
}

func (c cipher) encrypt(input int64) int64 {
	a := int32(input) + c.roundKeys[0]
	b := int32(uint64(input)>>32) + c.roundKeys[1]
	for r := 1; r <= roundCount; r++ {
		a = rotateLeft(a^b, b) + c.roundKeys[2*r]
		b = rotateLeft(b^a, a) + c.roundKeys[2*r+1]
	}
	return combine(a, b)
}

func (c cipher) decrypt(input int64) int64 {
	a := int32(input)
	b := int32(uint64(input) >> 32)
	for i := roundCount; i > 0; i-- {
		b = rotateRight(b-c.roundKeys[2*i+1], a) ^ a
		a = rotateRight(a-c.roundKeys[2*i], b) ^ b
	}
	b -= c.roundKeys[1]
	a -= c.roundKeys[0]
	return combine(a, b)
}

func rotateLeft(value int32, shift int32) int32 {
	return int32(value<<(shift&(32-1))) | int32(uint32(value)>>(32-(shift&(32-1))))
}

func rotateRight(value int32, shift int32) int32 {
	return int32(uint32(value)>>(shift&(32-1))) | int32(value<<(32-(shift&(32-1))))
}

func combine(high int32, low int32) int64 {
	return (int64(high) & 0xffffffff) | (int64(low) << 32)
}

オンラインツール

以下のURLで、ブラウザから直接ライセンスキーを生成できるオンラインツールも利用可能です。

https://www.zzzmode.com/mytools/charles/

本稿の内容は、技術的な学習と理解を目的としたものです。ライセンスの不正利用は法律に違反する可能性があります。正規ライセンスの購入を強く推奨します。

タグ: Charles ライセンスキー生成 Java Go TEA暗号

7月17日 19:37 投稿