Use a temporary solution for converting card IDs, shell out to a Python script

This commit is contained in:
skogaby
2019-01-17 00:07:05 -06:00
parent 76bfb02ae1
commit a4a78855ad
4 changed files with 586 additions and 440 deletions

View File

@@ -2,7 +2,6 @@ package com.buttongames.butterfly;
import com.buttongames.butterfly.http.ButterflyHttpServer;
import com.buttongames.butterfly.spring.configuration.ApplicationConfiguration;
import com.buttongames.butterfly.util.CardIdUtils;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
@@ -18,11 +17,9 @@ public class Main {
*/
public static void main(String[] args) {
// get the Spring context and start the server
/*applicationContext = new AnnotationConfigApplicationContext(ApplicationConfiguration.class);
applicationContext = new AnnotationConfigApplicationContext(ApplicationConfiguration.class);
final ButterflyHttpServer httpServer = applicationContext.getBean(ButterflyHttpServer.class);
httpServer.startServer();*/
System.out.println(CardIdUtils.encodeCardId("E004D62EDB3C9000"));
httpServer.startServer();
}
}

View File

@@ -1,449 +1,41 @@
package com.buttongames.butterfly.util;
import com.buttongames.butterfly.http.exception.CardCipherException;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.ByteStreams;
import java.util.Arrays;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
/**
* Utility class for converting between NFC IDs (the internal ID) and card numbers
* (the ID on the back of the card) for e-amusement passes.
* (the ID on the back of the card) for e-amusement passes. Right now it just calls out
* to a borrowed Python script, need to port this to a native implementation.
* @author anonymous
*/
public class CardIdUtils {
private static final int[] KEY = {
0x20d0d03c, 0x868ecb41, 0xbcd89c84, 0x4c0e0d0d,
0x84fc30ac, 0x4cc1890e, 0xfc5418a4, 0x02c50f44,
0x68acb4e0, 0x06cd4a4e, 0xcc28906c, 0x4f0c8ac0,
0xb03ca468, 0x884ac7c4, 0x389490d8, 0xcf80c6c2,
0x58d87404, 0xc48ec444, 0xb4e83c50, 0x498d0147,
0x64f454c0, 0x4c4701c8, 0xec302cc4, 0xc6c949c1,
0xc84c00f0, 0xcdcc49cc, 0x883c5cf4, 0x8b0fcb80,
0x703cc0b0, 0xcb820a8d, 0x78804c8c, 0x4fca830e,
0x80d0f03c, 0x8ec84f8c, 0x98c89c4c, 0xc80d878f,
0x54bc949c, 0xc801c5ce, 0x749078dc, 0xc3c80d46,
0x2c8070f0, 0x0cce4dcf, 0x8c3874e4, 0x8d448ac3,
0x987cac70, 0xc0c20ac5, 0x288cfc78, 0xc28543c8,
0x4c8c7434, 0xc50e4f8d, 0x8468f4b4, 0xcb4a0307,
0x2854dc98, 0x48430b45, 0x6858fce8, 0x4681cd49,
0xd04808ec, 0x458d0fcb, 0xe0a48ce4, 0x880f8fce,
0x7434b8fc, 0xce080a8e, 0x5860fc6c, 0x46c886cc,
0xd01098a4, 0xce090b8c, 0x1044cc2c, 0x86898e0f,
0xd0809c3c, 0x4a05860f, 0x54b4f80c, 0x4008870e,
0x1480b88c, 0x0ac8854f, 0x1c9034cc, 0x08444c4e,
0x0cb83c64, 0x41c08cc6, 0x1c083460, 0xc0c603ce,
0x2ca0645c, 0x818246cb, 0x0408e454, 0xc5464487,
0x88607c18, 0xc1424187, 0x284c7c90, 0xc1030509,
0x40486c94, 0x4603494b, 0xe0404ce4, 0x4109094d,
0x60443ce4, 0x4c0b8b8d, 0xe054e8bc, 0x02008e89,
};
private static final int[] LUT_A0 = {
0x02080008, 0x02082000, 0x00002008, 0x00000000,
0x02002000, 0x00080008, 0x02080000, 0x02082008,
0x00000008, 0x02000000, 0x00082000, 0x00002008,
0x00082008, 0x02002008, 0x02000008, 0x02080000,
0x00002000, 0x00082008, 0x00080008, 0x02002000,
0x02082008, 0x02000008, 0x00000000, 0x00082000,
0x02000000, 0x00080000, 0x02002008, 0x02080008,
0x00080000, 0x00002000, 0x02082000, 0x00000008,
0x00080000, 0x00002000, 0x02000008, 0x02082008,
0x00002008, 0x02000000, 0x00000000, 0x00082000,
0x02080008, 0x02002008, 0x02002000, 0x00080008,
0x02082000, 0x00000008, 0x00080008, 0x02002000,
0x02082008, 0x00080000, 0x02080000, 0x02000008,
0x00082000, 0x00002008, 0x02002008, 0x02080000,
0x00000008, 0x02082000, 0x00082008, 0x00000000,
0x02000000, 0x02080008, 0x00002000, 0x00082008,
};
private static final int[] LUT_A1 = {
0x08000004, 0x00020004, 0x00000000, 0x08020200,
0x00020004, 0x00000200, 0x08000204, 0x00020000,
0x00000204, 0x08020204, 0x00020200, 0x08000000,
0x08000200, 0x08000004, 0x08020000, 0x00020204,
0x00020000, 0x08000204, 0x08020004, 0x00000000,
0x00000200, 0x00000004, 0x08020200, 0x08020004,
0x08020204, 0x08020000, 0x08000000, 0x00000204,
0x00000004, 0x00020200, 0x00020204, 0x08000200,
0x00000204, 0x08000000, 0x08000200, 0x00020204,
0x08020200, 0x00020004, 0x00000000, 0x08000200,
0x08000000, 0x00000200, 0x08020004, 0x00020000,
0x00020004, 0x08020204, 0x00020200, 0x00000004,
0x08020204, 0x00020200, 0x00020000, 0x08000204,
0x08000004, 0x08020000, 0x00020204, 0x00000000,
0x00000200, 0x08000004, 0x08000204, 0x08020200,
0x08020000, 0x00000204, 0x00000004, 0x08020004,
};
private static final int[] LUT_A2 = {
0x80040100, 0x01000100, 0x80000000, 0x81040100,
0x00000000, 0x01040000, 0x81000100, 0x80040000,
0x01040100, 0x81000000, 0x01000000, 0x80000100,
0x81000000, 0x80040100, 0x00040000, 0x01000000,
0x81040000, 0x00040100, 0x00000100, 0x80000000,
0x00040100, 0x81000100, 0x01040000, 0x00000100,
0x80000100, 0x00000000, 0x80040000, 0x01040100,
0x01000100, 0x81040000, 0x81040100, 0x00040000,
0x81040000, 0x80000100, 0x00040000, 0x81000000,
0x00040100, 0x01000100, 0x80000000, 0x01040000,
0x81000100, 0x00000000, 0x00000100, 0x80040000,
0x00000000, 0x81040000, 0x01040100, 0x00000100,
0x01000000, 0x81040100, 0x80040100, 0x00040000,
0x81040100, 0x80000000, 0x01000100, 0x80040100,
0x80040000, 0x00040100, 0x01040000, 0x81000100,
0x80000100, 0x01000000, 0x81000000, 0x01040100,
};
private static final int[] LUT_A3 = {
0x04010801, 0x00000000, 0x00010800, 0x04010000,
0x04000001, 0x00000801, 0x04000800, 0x00010800,
0x00000800, 0x04010001, 0x00000001, 0x04000800,
0x00010001, 0x04010800, 0x04010000, 0x00000001,
0x00010000, 0x04000801, 0x04010001, 0x00000800,
0x00010801, 0x04000000, 0x00000000, 0x00010001,
0x04000801, 0x00010801, 0x04010800, 0x04000001,
0x04000000, 0x00010000, 0x00000801, 0x04010801,
0x00010001, 0x04010800, 0x04000800, 0x00010801,
0x04010801, 0x00010001, 0x04000001, 0x00000000,
0x04000000, 0x00000801, 0x00010000, 0x04010001,
0x00000800, 0x04000000, 0x00010801, 0x04000801,
0x04010800, 0x00000800, 0x00000000, 0x04000001,
0x00000001, 0x04010801, 0x00010800, 0x04010000,
0x04010001, 0x00010000, 0x00000801, 0x04000800,
0x04000801, 0x00000001, 0x04010000, 0x00010800,
};
private static final int[] LUT_B0 = {
0x00000400, 0x00000020, 0x00100020, 0x40100000,
0x40100420, 0x40000400, 0x00000420, 0x00000000,
0x00100000, 0x40100020, 0x40000020, 0x00100400,
0x40000000, 0x00100420, 0x00100400, 0x40000020,
0x40100020, 0x00000400, 0x40000400, 0x40100420,
0x00000000, 0x00100020, 0x40100000, 0x00000420,
0x40100400, 0x40000420, 0x00100420, 0x40000000,
0x40000420, 0x40100400, 0x00000020, 0x00100000,
0x40000420, 0x00100400, 0x40100400, 0x40000020,
0x00000400, 0x00000020, 0x00100000, 0x40100400,
0x40100020, 0x40000420, 0x00000420, 0x00000000,
0x00000020, 0x40100000, 0x40000000, 0x00100020,
0x00000000, 0x40100020, 0x00100020, 0x00000420,
0x40000020, 0x00000400, 0x40100420, 0x00100000,
0x00100420, 0x40000000, 0x40000400, 0x40100420,
0x40100000, 0x00100420, 0x00100400, 0x40000400,
};
private static final int[] LUT_B1 = {
0x00800000, 0x00001000, 0x00000040, 0x00801042,
0x00801002, 0x00800040, 0x00001042, 0x00801000,
0x00001000, 0x00000002, 0x00800002, 0x00001040,
0x00800042, 0x00801002, 0x00801040, 0x00000000,
0x00001040, 0x00800000, 0x00001002, 0x00000042,
0x00800040, 0x00001042, 0x00000000, 0x00800002,
0x00000002, 0x00800042, 0x00801042, 0x00001002,
0x00801000, 0x00000040, 0x00000042, 0x00801040,
0x00801040, 0x00800042, 0x00001002, 0x00801000,
0x00001000, 0x00000002, 0x00800002, 0x00800040,
0x00800000, 0x00001040, 0x00801042, 0x00000000,
0x00001042, 0x00800000, 0x00000040, 0x00001002,
0x00800042, 0x00000040, 0x00000000, 0x00801042,
0x00801002, 0x00801040, 0x00000042, 0x00001000,
0x00001040, 0x00801002, 0x00800040, 0x00000042,
0x00000002, 0x00001042, 0x00801000, 0x00800002,
};
private static final int[] LUT_B2 = {
0x10400000, 0x00404010, 0x00000010, 0x10400010,
0x10004000, 0x00400000, 0x10400010, 0x00004010,
0x00400010, 0x00004000, 0x00404000, 0x10000000,
0x10404010, 0x10000010, 0x10000000, 0x10404000,
0x00000000, 0x10004000, 0x00404010, 0x00000010,
0x10000010, 0x10404010, 0x00004000, 0x10400000,
0x10404000, 0x00400010, 0x10004010, 0x00404000,
0x00004010, 0x00000000, 0x00400000, 0x10004010,
0x00404010, 0x00000010, 0x10000000, 0x00004000,
0x10000010, 0x10004000, 0x00404000, 0x10400010,
0x00000000, 0x00404010, 0x00004010, 0x10404000,
0x10004000, 0x00400000, 0x10404010, 0x10000000,
0x10004010, 0x10400000, 0x00400000, 0x10404010,
0x00004000, 0x00400010, 0x10400010, 0x00004010,
0x00400010, 0x00000000, 0x10404000, 0x10000010,
0x10400000, 0x10004010, 0x00000010, 0x00404000,
};
private static final int[] LUT_B3 = {
0x00208080, 0x00008000, 0x20200000, 0x20208080,
0x00200000, 0x20008080, 0x20008000, 0x20200000,
0x20008080, 0x00208080, 0x00208000, 0x20000080,
0x20200080, 0x00200000, 0x00000000, 0x20008000,
0x00008000, 0x20000000, 0x00200080, 0x00008080,
0x20208080, 0x00208000, 0x20000080, 0x00200080,
0x20000000, 0x00000080, 0x00008080, 0x20208000,
0x00000080, 0x20200080, 0x20208000, 0x00000000,
0x00000000, 0x20208080, 0x00200080, 0x20008000,
0x00208080, 0x00008000, 0x20000080, 0x00200080,
0x20208000, 0x00000080, 0x00008080, 0x20200000,
0x20008080, 0x20000000, 0x20200000, 0x00208000,
0x20208080, 0x00008080, 0x00208000, 0x20200080,
0x00200000, 0x20000080, 0x20008000, 0x00000000,
0x00008000, 0x00200000, 0x20200080, 0x00208080,
0x20000000, 0x20208000, 0x00000080, 0x20008080,
};
private static final String VALID_CHARS = "0123456789ABCDEFGHJKLMNPRSTUWXYZ";
private static final ImmutableMap<String, String> CONV_CHARS = ImmutableMap.of("I", "1", "O", "0");
/**
* Converts an internal NFC ID to a display ID for e-amusement passes.
* @param id The NFC ID for the pass
* @return The display ID for the pass
*/
public static String encodeCardId(final String id) {
if (id.length() != 16) {
throw new CardCipherException("Expected a 16-character card ID");
try {
// TODO: Port the python script
final Path path = Paths.get(ClassLoader.getSystemResource("cardconv.py").toURI());
final ProcessBuilder builder = new ProcessBuilder("python",
"-c",
"import cardconv; print(cardconv.CardCipher.encode('" + id + "'));");
builder.directory(path.getParent().toFile());
final Process process = builder.start();
final InputStream stdout = process.getInputStream();
return new String(ByteStreams.toByteArray(stdout), StandardCharsets.UTF_8).trim();
} catch (Exception e) {
e.printStackTrace();
return null;
}
final byte[] cardint = CollectionUtils.hexStringToByteArray(id);
// reverse bytes
final byte[] reverse = new byte[8];
for (int i = 0; i < 8; i++) {
reverse[7 - i] = cardint[i];
}
// encipher
final byte[] ciphered = encodeHelper(reverse);
// convert 8 x 8 bit bytes into 13 x 5 bit groups (sort of)
final byte[] bits = new byte[65];
for (int i = 0; i < 64; i++) {
bits[i] = (byte)((ciphered[i >> 3] >> (~i & 7)) & i);
}
final byte[] groups = new byte[16];
for (int i = 0; i < 13; i++) {
groups[i] = (byte)((bits[i * 5 + 0] << 4) |
(bits[i * 5 + 1] << 3) |
(bits[i * 5 + 2] << 2) |
(bits[i * 5 + 3] << 1) |
(bits[i * 5 + 4] << 0));
}
// smear 13 groups out into 14 groups
groups[13] = 1;
groups[0] ^= typeFromCardId(id);
groups[0] ^= groups[15];
for (int i = 1; i < 14; i++) {
groups[i] ^= groups[i - 1];
}
// scheme field is 1 for old-style, 2 for felica cards
groups[14] = (byte) typeFromCardId(id);
groups[15] = checksum(groups);
// convert to chars and return
String encodedId = "";
for (int i = 0; i < groups.length; i++) {
encodedId += VALID_CHARS.charAt(groups[i]);
}
return encodedId;
}
private static final byte[] encodeHelper(final byte[] inBytes) {
if (inBytes.length != 8) {
throw new CardCipherException("Trouble encoding the card ID");
}
final byte[] inp = Arrays.copyOf(inBytes, inBytes.length);
final byte[] out = { 0, 0, 0, 0, 0, 0, 0, 0 };
fromInt(out, operatorA(0x00, toInt(inp)));
fromInt(out, operatorB(0x20, toInt(out)));
fromInt(out, operatorA(0x40, toInt(out)));
return out;
}
private static final byte[] decodeHelper(final byte[] inBytes) {
if (inBytes.length != 8) {
throw new CardCipherException("Trouble decoding the card ID");
}
final byte[] inp = Arrays.copyOf(inBytes, inBytes.length);
final byte[] out = { 0, 0, 0, 0, 0, 0, 0, 0 };
fromInt(out, operatorB(0x40, toInt(inp)));
fromInt(out, operatorA(0x20, toInt(out)));
fromInt(out, operatorB(0x00, toInt(out)));
return out;
}
private static int typeFromCardId(final String cardId) {
final String upperCardId = cardId.toUpperCase();
if (upperCardId.startsWith("E0")) {
return 1;
} else if (upperCardId.startsWith("01")) {
return 2;
} else {
throw new CardCipherException("Unrecognized card type");
}
}
private static byte checksum(final byte[] data) {
byte checksum = 0;
for (int i = 0; i < 16; i++) {
checksum += (i % 3 + 1) * data[i];
}
while (checksum >= 0x20) {
checksum = (byte)((checksum & 0x1F) + (checksum >> 5));
}
return checksum;
}
private static int toInt(final byte[] data) {
final int inX = (data[0] & 0xFF) |
((data[1] & 0xFF) << 8) |
((data[2] & 0xFF) << 16) |
((data[3] & 0xFF) << 24);
final int inY = (data[4] & 0xFF) |
((data[5] & 0xFF) << 8) |
((data[6] & 0xFF) << 16) |
((data[7] & 0xFF) << 24);
final int v7 = ((((inX ^ (inY >> 4)) & 0xF0F0F0F) << 4) ^ inY) & 0xFFFFFFFF;
final int v8 = (((inX ^ (inY >> 4)) & 0xF0F0F0F) ^ inX) & 0xFFFFFFFF;
final int v9 = ((v7 ^ (v8 >> 16))) & 0x0000FFFF;
final int v10 = (((v7 ^ (v8 >> 16)) << 16) ^ v8) & 0xFFFFFFFF;
final int v11 = (v9 ^ v7) & 0xFFFFFFFF;
final int v12 = (v10 ^ (v11 >> 2)) & 0x33333333;
final int v13 = (v11 ^ (v12 << 2)) & 0xFFFFFFFF;
final int v14 = (v12 ^ v10) & 0xFFFFFFFF;
final int v15 = (v13 ^ (v14 >> 8)) & 0x00FF00FF;
final int v16 = (v14 ^ (v15 << 8)) & 0xFFFFFFFF;
final int v17 = ror(v15 ^ v13, 1);
final int v18 = (v16 ^ v17) & 0x55555555;
final int v3 = ror(v18 ^ v16, 1);
final int v4 = (v18 ^ v17) & 0xFFFFFFFF;
return ((v3 & 0xFFFFFFFF) << 32) | (v4 & 0xFFFFFFFF);
}
private static void fromInt(final byte[] data, final int state) {
final int v3 = (state >> 32) & 0xFFFFFFFF;
final int v4 = state & 0xFFFFFFFF;
final int v22 = ror(v4, 31);
final int v23 = (v3 ^ v22) & 0x55555555;
final int v24 = (v23 ^ v22) & 0xFFFFFFFF;
final int v25 = ror(v23 ^ v3, 31);
final int v26 = (v25 ^ (v24 >> 8)) & 0x00FF00FF;
final int v27 = (v24 ^ (v26 << 8)) & 0xFFFFFFFF;
final int v28 = (v26 ^ v25) & 0xFFFFFFFF;
final int v29 = ((v28 >> 2) ^ v27) & 0x33333333;
final int v30 = ((v29 << 2) ^ v28) & 0xFFFFFFFF;
final int v31 = (v29 ^ v27) & 0xFFFFFFFF;
final int v32 = (v30 ^ (v31 >> 16)) & 0x0000FFFF;
final int v33 = (v31 ^ (v32 << 16)) & 0xFFFFFFFF;
final int v34 = (v32 ^ v30) & 0xFFFFFFFF;
final int v35 = (v33 ^ (v34 >> 4)) & 0xF0F0F0F;
final int outY = ((v35 << 4) ^ v34) & 0xFFFFFFFF;
final int outX = (v35 ^ v33) & 0xFFFFFFFF;
data[0] = (byte)(outX & 0xFF);
data[1] = (byte)((outX >> 8) & 0xFF);
data[2] = (byte)((outX >> 16) & 0xFF);
data[3] = (byte)((outX >> 24) & 0xFF);
data[4] = (byte)(outY & 0xFF);
data[5] = (byte)((outY >> 8) & 0xFF);
data[6] = (byte)((outY >> 16) & 0xFF);
data[7] = (byte)((outY >> 24) & 0xFF);
}
private static int operatorA(final int off, final int state) {
int v3 = (state >> 32) & 0xFFFFFFFF;
int v4 = state & 0xFFFFFFFF;
for (int i = 0; i < 32; i += 4) {
final int v20 = ror(v3 ^ KEY[off + i + 1], 28);
v4 ^= LUT_B0[(v20 >> 26) & 0x3F] ^
LUT_B1[(v20 >> 18) & 0x3F] ^
LUT_B2[(v20 >> 10) & 0x3F] ^
LUT_B3[(v20 >> 2) & 0x3F] ^
LUT_A0[((v3 ^ KEY[off + i]) >> 26) & 0x3F] ^
LUT_A1[((v3 ^ KEY[off + i]) >> 18) & 0x3F] ^
LUT_A2[((v3 ^ KEY[off + i]) >> 10) & 0x3F] ^
LUT_A3[((v3 ^ KEY[off + i]) >> 2) & 0x3F];
final int v21 = ror(v4 ^ KEY[off + i + 3], 28);
v3 ^= LUT_B0[(v21 >> 26) & 0x3F] ^
LUT_B1[(v21 >> 18) & 0x3F] ^
LUT_B2[(v21 >> 10) & 0x3F] ^
LUT_B3[(v21 >> 2) & 0x3F] ^
LUT_A0[((v4 ^ KEY[off + i + 2]) >> 26) & 0x3F] ^
LUT_A1[((v4 ^ KEY[off + i + 2]) >> 18) & 0x3F] ^
LUT_A2[((v4 ^ KEY[off + i + 2]) >> 10) & 0x3F] ^
LUT_A3[((v4 ^ KEY[off + i + 2]) >> 2) & 0x3F];
}
return ((v3 & 0xFFFFFFFF) << 32) |
(v4 & 0xFFFFFFFF);
}
private static int operatorB(final int off, final int state) {
int v3 = (state >> 32) & 0xFFFFFFFF;
int v4 = state & 0xFFFFFFFF;
for (int i = 0; i < 32; i += 4) {
final int v20 = ror(v3 ^ KEY[off + 31 - i], 28);
v4 ^= LUT_A0[((v3 ^ KEY[off + 30 - i]) >> 26) & 0x3F] ^
LUT_A1[((v3 ^ KEY[off + 30 - i]) >> 18) & 0x3F] ^
LUT_A2[((v3 ^ KEY[off + 30 - i]) >> 10) & 0x3F] ^
LUT_A3[((v3 ^ KEY[off + 30 - i]) >> 2) & 0x3F] ^
LUT_B0[(v20 >> 26) & 0x3F] ^
LUT_B1[(v20 >> 18) & 0x3F] ^
LUT_B2[(v20 >> 10) & 0x3F] ^
LUT_B3[(v20 >> 2) & 0x3F];
final int v21 = ror(v4 ^ KEY[off + 29 - i], 28);
v3 ^= LUT_A0[((v4 ^ KEY[off + 28 - i]) >> 26) & 0x3F] ^
LUT_A1[((v4 ^ KEY[off + 28 - i]) >> 18) & 0x3F] ^
LUT_A2[((v4 ^ KEY[off + 28 - i]) >> 10) & 0x3F] ^
LUT_A3[((v4 ^ KEY[off + 28 - i]) >> 2) & 0x3F] ^
LUT_B0[(v21 >> 26) & 0x3F] ^
LUT_B1[(v21 >> 18) & 0x3F] ^
LUT_B2[(v21 >> 10) & 0x3F] ^
LUT_B3[(v21 >> 2) & 0x3F];
}
return ((v3 & 0xFFFFFFFF) << 32) |
(v4 & 0xFFFFFFFF);
}
private static int ror(final int value, final int amount) {
return ((value << (32 - amount)) & 0xFFFFFFFF) |
((value >> amount) & 0xFFFFFFFF);
}
}

View File

@@ -39,5 +39,23 @@ public class CollectionUtils {
return data;
}
/**
* Converts a hex string into an integer array, where each int
* is an unsigned byte value.
* @param s
* @return
*/
public static int[] hexStringToUnsignedByteArray(final String s) {
final int len = s.length();
final int[] data = new int[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i + 1), 16);
}
return data;
}
}

View File

@@ -0,0 +1,539 @@
from typing import List
class CardCipherException(Exception):
pass
class CardCipher:
"""
Algorithm for converting between the Card ID as stored in an
eAmusement card and the 16 character card string as shown on
the back of a card and in-game.
"""
KEY = [
0x20d0d03c, 0x868ecb41, 0xbcd89c84, 0x4c0e0d0d,
0x84fc30ac, 0x4cc1890e, 0xfc5418a4, 0x02c50f44,
0x68acb4e0, 0x06cd4a4e, 0xcc28906c, 0x4f0c8ac0,
0xb03ca468, 0x884ac7c4, 0x389490d8, 0xcf80c6c2,
0x58d87404, 0xc48ec444, 0xb4e83c50, 0x498d0147,
0x64f454c0, 0x4c4701c8, 0xec302cc4, 0xc6c949c1,
0xc84c00f0, 0xcdcc49cc, 0x883c5cf4, 0x8b0fcb80,
0x703cc0b0, 0xcb820a8d, 0x78804c8c, 0x4fca830e,
0x80d0f03c, 0x8ec84f8c, 0x98c89c4c, 0xc80d878f,
0x54bc949c, 0xc801c5ce, 0x749078dc, 0xc3c80d46,
0x2c8070f0, 0x0cce4dcf, 0x8c3874e4, 0x8d448ac3,
0x987cac70, 0xc0c20ac5, 0x288cfc78, 0xc28543c8,
0x4c8c7434, 0xc50e4f8d, 0x8468f4b4, 0xcb4a0307,
0x2854dc98, 0x48430b45, 0x6858fce8, 0x4681cd49,
0xd04808ec, 0x458d0fcb, 0xe0a48ce4, 0x880f8fce,
0x7434b8fc, 0xce080a8e, 0x5860fc6c, 0x46c886cc,
0xd01098a4, 0xce090b8c, 0x1044cc2c, 0x86898e0f,
0xd0809c3c, 0x4a05860f, 0x54b4f80c, 0x4008870e,
0x1480b88c, 0x0ac8854f, 0x1c9034cc, 0x08444c4e,
0x0cb83c64, 0x41c08cc6, 0x1c083460, 0xc0c603ce,
0x2ca0645c, 0x818246cb, 0x0408e454, 0xc5464487,
0x88607c18, 0xc1424187, 0x284c7c90, 0xc1030509,
0x40486c94, 0x4603494b, 0xe0404ce4, 0x4109094d,
0x60443ce4, 0x4c0b8b8d, 0xe054e8bc, 0x02008e89,
]
LUT_A0 = [
0x02080008, 0x02082000, 0x00002008, 0x00000000,
0x02002000, 0x00080008, 0x02080000, 0x02082008,
0x00000008, 0x02000000, 0x00082000, 0x00002008,
0x00082008, 0x02002008, 0x02000008, 0x02080000,
0x00002000, 0x00082008, 0x00080008, 0x02002000,
0x02082008, 0x02000008, 0x00000000, 0x00082000,
0x02000000, 0x00080000, 0x02002008, 0x02080008,
0x00080000, 0x00002000, 0x02082000, 0x00000008,
0x00080000, 0x00002000, 0x02000008, 0x02082008,
0x00002008, 0x02000000, 0x00000000, 0x00082000,
0x02080008, 0x02002008, 0x02002000, 0x00080008,
0x02082000, 0x00000008, 0x00080008, 0x02002000,
0x02082008, 0x00080000, 0x02080000, 0x02000008,
0x00082000, 0x00002008, 0x02002008, 0x02080000,
0x00000008, 0x02082000, 0x00082008, 0x00000000,
0x02000000, 0x02080008, 0x00002000, 0x00082008,
]
LUT_A1 = [
0x08000004, 0x00020004, 0x00000000, 0x08020200,
0x00020004, 0x00000200, 0x08000204, 0x00020000,
0x00000204, 0x08020204, 0x00020200, 0x08000000,
0x08000200, 0x08000004, 0x08020000, 0x00020204,
0x00020000, 0x08000204, 0x08020004, 0x00000000,
0x00000200, 0x00000004, 0x08020200, 0x08020004,
0x08020204, 0x08020000, 0x08000000, 0x00000204,
0x00000004, 0x00020200, 0x00020204, 0x08000200,
0x00000204, 0x08000000, 0x08000200, 0x00020204,
0x08020200, 0x00020004, 0x00000000, 0x08000200,
0x08000000, 0x00000200, 0x08020004, 0x00020000,
0x00020004, 0x08020204, 0x00020200, 0x00000004,
0x08020204, 0x00020200, 0x00020000, 0x08000204,
0x08000004, 0x08020000, 0x00020204, 0x00000000,
0x00000200, 0x08000004, 0x08000204, 0x08020200,
0x08020000, 0x00000204, 0x00000004, 0x08020004,
]
LUT_A2 = [
0x80040100, 0x01000100, 0x80000000, 0x81040100,
0x00000000, 0x01040000, 0x81000100, 0x80040000,
0x01040100, 0x81000000, 0x01000000, 0x80000100,
0x81000000, 0x80040100, 0x00040000, 0x01000000,
0x81040000, 0x00040100, 0x00000100, 0x80000000,
0x00040100, 0x81000100, 0x01040000, 0x00000100,
0x80000100, 0x00000000, 0x80040000, 0x01040100,
0x01000100, 0x81040000, 0x81040100, 0x00040000,
0x81040000, 0x80000100, 0x00040000, 0x81000000,
0x00040100, 0x01000100, 0x80000000, 0x01040000,
0x81000100, 0x00000000, 0x00000100, 0x80040000,
0x00000000, 0x81040000, 0x01040100, 0x00000100,
0x01000000, 0x81040100, 0x80040100, 0x00040000,
0x81040100, 0x80000000, 0x01000100, 0x80040100,
0x80040000, 0x00040100, 0x01040000, 0x81000100,
0x80000100, 0x01000000, 0x81000000, 0x01040100,
]
LUT_A3 = [
0x04010801, 0x00000000, 0x00010800, 0x04010000,
0x04000001, 0x00000801, 0x04000800, 0x00010800,
0x00000800, 0x04010001, 0x00000001, 0x04000800,
0x00010001, 0x04010800, 0x04010000, 0x00000001,
0x00010000, 0x04000801, 0x04010001, 0x00000800,
0x00010801, 0x04000000, 0x00000000, 0x00010001,
0x04000801, 0x00010801, 0x04010800, 0x04000001,
0x04000000, 0x00010000, 0x00000801, 0x04010801,
0x00010001, 0x04010800, 0x04000800, 0x00010801,
0x04010801, 0x00010001, 0x04000001, 0x00000000,
0x04000000, 0x00000801, 0x00010000, 0x04010001,
0x00000800, 0x04000000, 0x00010801, 0x04000801,
0x04010800, 0x00000800, 0x00000000, 0x04000001,
0x00000001, 0x04010801, 0x00010800, 0x04010000,
0x04010001, 0x00010000, 0x00000801, 0x04000800,
0x04000801, 0x00000001, 0x04010000, 0x00010800,
]
LUT_B0 = [
0x00000400, 0x00000020, 0x00100020, 0x40100000,
0x40100420, 0x40000400, 0x00000420, 0x00000000,
0x00100000, 0x40100020, 0x40000020, 0x00100400,
0x40000000, 0x00100420, 0x00100400, 0x40000020,
0x40100020, 0x00000400, 0x40000400, 0x40100420,
0x00000000, 0x00100020, 0x40100000, 0x00000420,
0x40100400, 0x40000420, 0x00100420, 0x40000000,
0x40000420, 0x40100400, 0x00000020, 0x00100000,
0x40000420, 0x00100400, 0x40100400, 0x40000020,
0x00000400, 0x00000020, 0x00100000, 0x40100400,
0x40100020, 0x40000420, 0x00000420, 0x00000000,
0x00000020, 0x40100000, 0x40000000, 0x00100020,
0x00000000, 0x40100020, 0x00100020, 0x00000420,
0x40000020, 0x00000400, 0x40100420, 0x00100000,
0x00100420, 0x40000000, 0x40000400, 0x40100420,
0x40100000, 0x00100420, 0x00100400, 0x40000400,
]
LUT_B1 = [
0x00800000, 0x00001000, 0x00000040, 0x00801042,
0x00801002, 0x00800040, 0x00001042, 0x00801000,
0x00001000, 0x00000002, 0x00800002, 0x00001040,
0x00800042, 0x00801002, 0x00801040, 0x00000000,
0x00001040, 0x00800000, 0x00001002, 0x00000042,
0x00800040, 0x00001042, 0x00000000, 0x00800002,
0x00000002, 0x00800042, 0x00801042, 0x00001002,
0x00801000, 0x00000040, 0x00000042, 0x00801040,
0x00801040, 0x00800042, 0x00001002, 0x00801000,
0x00001000, 0x00000002, 0x00800002, 0x00800040,
0x00800000, 0x00001040, 0x00801042, 0x00000000,
0x00001042, 0x00800000, 0x00000040, 0x00001002,
0x00800042, 0x00000040, 0x00000000, 0x00801042,
0x00801002, 0x00801040, 0x00000042, 0x00001000,
0x00001040, 0x00801002, 0x00800040, 0x00000042,
0x00000002, 0x00001042, 0x00801000, 0x00800002,
]
LUT_B2 = [
0x10400000, 0x00404010, 0x00000010, 0x10400010,
0x10004000, 0x00400000, 0x10400010, 0x00004010,
0x00400010, 0x00004000, 0x00404000, 0x10000000,
0x10404010, 0x10000010, 0x10000000, 0x10404000,
0x00000000, 0x10004000, 0x00404010, 0x00000010,
0x10000010, 0x10404010, 0x00004000, 0x10400000,
0x10404000, 0x00400010, 0x10004010, 0x00404000,
0x00004010, 0x00000000, 0x00400000, 0x10004010,
0x00404010, 0x00000010, 0x10000000, 0x00004000,
0x10000010, 0x10004000, 0x00404000, 0x10400010,
0x00000000, 0x00404010, 0x00004010, 0x10404000,
0x10004000, 0x00400000, 0x10404010, 0x10000000,
0x10004010, 0x10400000, 0x00400000, 0x10404010,
0x00004000, 0x00400010, 0x10400010, 0x00004010,
0x00400010, 0x00000000, 0x10404000, 0x10000010,
0x10400000, 0x10004010, 0x00000010, 0x00404000,
]
LUT_B3 = [
0x00208080, 0x00008000, 0x20200000, 0x20208080,
0x00200000, 0x20008080, 0x20008000, 0x20200000,
0x20008080, 0x00208080, 0x00208000, 0x20000080,
0x20200080, 0x00200000, 0x00000000, 0x20008000,
0x00008000, 0x20000000, 0x00200080, 0x00008080,
0x20208080, 0x00208000, 0x20000080, 0x00200080,
0x20000000, 0x00000080, 0x00008080, 0x20208000,
0x00000080, 0x20200080, 0x20208000, 0x00000000,
0x00000000, 0x20208080, 0x00200080, 0x20008000,
0x00208080, 0x00008000, 0x20000080, 0x00200080,
0x20208000, 0x00000080, 0x00008080, 0x20200000,
0x20008080, 0x20000000, 0x20200000, 0x00208000,
0x20208080, 0x00008080, 0x00208000, 0x20200080,
0x00200000, 0x20000080, 0x20008000, 0x00000000,
0x00008000, 0x00200000, 0x20200080, 0x00208080,
0x20000000, 0x20208000, 0x00000080, 0x20008080,
]
VALID_CHARS = "0123456789ABCDEFGHJKLMNPRSTUWXYZ"
CONV_CHARS = {
"I": "1",
"O": "0",
}
@staticmethod
def __type_from_cardid(cardid: str) -> int:
if cardid[:2].upper() == 'E0':
return 1
if cardid[:2].upper() == '01':
return 2
raise CardCipherException('Unrecognized card type')
@staticmethod
def encode(cardid: str) -> str:
"""
Given a card ID as stored on a card (Usually starting with E004), convert
it to the card string as shown on the back of the card.
Parameters:
cardid - 16 digit card ID (hex values stored as string).
Returns:
String representation of the card string.
"""
if len(cardid) != 16:
raise CardCipherException(
'Expected 16-character card ID, got {}'.format(len(cardid)),
)
cardint = [int(cardid[i:i+2], 16) for i in range(0, len(cardid), 2)]
# Reverse bytes
reverse = [0] * 8
for i in range(0, 8):
reverse[7 - i] = cardint[i]
# Encipher
ciphered = CardCipher.__encode(bytes(reverse))
# Convert 8 x 8 bit bytes into 13 x 5 bit groups (sort of)
bits = [0] * 65
for i in range(0, 64):
bits[i] = (ciphered[i >> 3] >> (~i & 7)) & 1
groups = [0] * 16
for i in range(0, 13):
groups[i] = (
(bits[i * 5 + 0] << 4) |
(bits[i * 5 + 1] << 3) |
(bits[i * 5 + 2] << 2) |
(bits[i * 5 + 3] << 1) |
(bits[i * 5 + 4] << 0)
)
# Smear 13 groups out into 14 groups
groups[13] = 1
groups[0] ^= CardCipher.__type_from_cardid(cardid)
for i in range(0, 14):
groups[i] ^= groups[i - 1]
# Scheme field is 1 for old-style, 2 for felica cards
groups[14] = CardCipher.__type_from_cardid(cardid)
groups[15] = CardCipher.__checksum(groups)
# Convert to chars and return
return ''.join([CardCipher.VALID_CHARS[i] for i in groups])
@staticmethod
def decode(cardid: str) -> str:
"""
Given a card string as shown on the back of the card, return the card ID
as stored on the card itself. Does some sanitization to remove dashes,
spaces and convert confusing characters (1, L and 0, O) before converting.
Parameters:
cardid - String representation of the card string.
Returns:
16 digit card ID (hex values stored as string).
"""
# First sanitize the input
cardid = cardid.replace(' ', '')
cardid = cardid.replace('-', '')
cardid = cardid.upper()
for c in CardCipher.CONV_CHARS:
cardid = cardid.replace(c, CardCipher.CONV_CHARS[c])
if len(cardid) != 16:
raise CardCipherException(
'Expected 16-character card ID, got {}'.format(len(cardid)),
)
for c in cardid:
if c not in CardCipher.VALID_CHARS:
raise CardCipherException(
'Got unexpected character {} in card ID'.format(c),
)
# Convert chars to groups
groups = [0] * 16
for i in range(0, 16):
for j in range(0, 32):
if cardid[i] == CardCipher.VALID_CHARS[j]:
groups[i] = j
break
# Verify scheme and checksum
if (groups[14] != 1 and groups[14] != 2):
raise CardCipherException(
"Unrecognized card type"
)
if groups[15] != CardCipher.__checksum(groups):
raise CardCipherException(
"Bad card number"
)
# Un-smear 14 fields back into 13
for i in range(13, 0, -1):
groups[i] ^= groups[i - 1]
groups[0] ^= groups[14]
# Explode groups into bits
bits = [0] * 64
for i in range(0, 64):
bits[i] = (groups[int(i / 5)] >> (4 - (i % 5))) & 1
# Re-pack bits into eight bytes
ciphered = [0] * 8
for i in range(0, 64):
ciphered[int(i / 8)] |= bits[i] << (~i & 7)
# Decipher and reverse
deciphered = CardCipher.__decode(bytes(ciphered))
reverse = [0] * 8
for i in range(0, 8):
reverse[i] = deciphered[7 - i]
def tohex(x: int) -> str:
h = hex(x)[2:]
while len(h) < 2:
h = '0' + h
return h.upper()
# Convert to a string, verify we have the same type
finalvalue = ''.join([tohex(v) for v in reverse])
if groups[14] != CardCipher.__type_from_cardid(finalvalue):
raise CardCipherException(
"Card type mismatch"
)
return finalvalue
@staticmethod
def __checksum(data: List[int]) -> int:
checksum = 0
for i in range(0, 15):
checksum += (i % 3 + 1) * data[i]
while checksum >= 0x20:
checksum = (checksum & 0x1F) + (checksum >> 5)
return checksum
@staticmethod
def __encode(inbytes: bytes) -> bytes:
if len(inbytes) != 8:
raise CardCipherException(
'Expected 8-byte input, got {}'.format(len(inbytes)),
)
inp = [b for b in inbytes]
out = [0] * 8
CardCipher.__from_int(out, CardCipher.__operatorA(0x00, CardCipher.__to_int(inp)))
CardCipher.__from_int(out, CardCipher.__operatorB(0x20, CardCipher.__to_int(out)))
CardCipher.__from_int(out, CardCipher.__operatorA(0x40, CardCipher.__to_int(out)))
return bytes(out)
@staticmethod
def __decode(inbytes: bytes) -> bytes:
if len(inbytes) != 8:
raise CardCipherException(
'Expected 8-byte input, got {}'.format(len(inbytes)),
)
inp = [b for b in inbytes]
out = [0] * 8
CardCipher.__from_int(out, CardCipher.__operatorB(0x40, CardCipher.__to_int(inp)))
CardCipher.__from_int(out, CardCipher.__operatorA(0x20, CardCipher.__to_int(out)))
CardCipher.__from_int(out, CardCipher.__operatorB(0x00, CardCipher.__to_int(out)))
return bytes(out)
@staticmethod
def __to_int(data: List[int]) -> int:
inX = (
(data[0] & 0xFF) |
((data[1] & 0xFF) << 8) |
((data[2] & 0xFF) << 16) |
((data[3] & 0xFF) << 24)
)
inY = (
(data[4] & 0xFF) |
((data[5] & 0xFF) << 8) |
((data[6] & 0xFF) << 16) |
((data[7] & 0xFF) << 24)
)
v7 = ((((inX ^ (inY >> 4)) & 0xF0F0F0F) << 4) ^ inY) & 0xFFFFFFFF
v8 = (((inX ^ (inY >> 4)) & 0xF0F0F0F) ^ inX) & 0xFFFFFFFF
v9 = ((v7 ^ (v8 >> 16))) & 0x0000FFFF
v10 = (((v7 ^ (v8 >> 16)) << 16) ^ v8) & 0xFFFFFFFF
v11 = (v9 ^ v7) & 0xFFFFFFFF
v12 = (v10 ^ (v11 >> 2)) & 0x33333333
v13 = (v11 ^ (v12 << 2)) & 0xFFFFFFFF
v14 = (v12 ^ v10) & 0xFFFFFFFF
v15 = (v13 ^ (v14 >> 8)) & 0x00FF00FF
v16 = (v14 ^ (v15 << 8)) & 0xFFFFFFFF
v17 = CardCipher.__ror(v15 ^ v13, 1)
v18 = (v16 ^ v17) & 0x55555555
v3 = CardCipher.__ror(v18 ^ v16, 1)
v4 = (v18 ^ v17) & 0xFFFFFFFF
return ((v3 & 0xFFFFFFFF) << 32) | (v4 & 0xFFFFFFFF)
@staticmethod
def __from_int(data: List[int], state: int) -> None:
v3 = (state >> 32) & 0xFFFFFFFF
v4 = state & 0xFFFFFFFF
v22 = CardCipher.__ror(v4, 31)
v23 = (v3 ^ v22) & 0x55555555
v24 = (v23 ^ v22) & 0xFFFFFFFF
v25 = CardCipher.__ror(v23 ^ v3, 31)
v26 = (v25 ^ (v24 >> 8)) & 0x00FF00FF
v27 = (v24 ^ (v26 << 8)) & 0xFFFFFFFF
v28 = (v26 ^ v25) & 0xFFFFFFFF
v29 = ((v28 >> 2) ^ v27) & 0x33333333
v30 = ((v29 << 2) ^ v28) & 0xFFFFFFFF
v31 = (v29 ^ v27) & 0xFFFFFFFF
v32 = (v30 ^ (v31 >> 16)) & 0x0000FFFF
v33 = (v31 ^ (v32 << 16)) & 0xFFFFFFFF
v34 = (v32 ^ v30) & 0xFFFFFFFF
v35 = (v33 ^ (v34 >> 4)) & 0xF0F0F0F
outY = ((v35 << 4) ^ v34) & 0xFFFFFFFF
outX = (v35 ^ v33) & 0xFFFFFFFF
data[0] = outX & 0xFF
data[1] = (outX >> 8) & 0xFF
data[2] = (outX >> 16) & 0xFF
data[3] = (outX >> 24) & 0xFF
data[4] = outY & 0xFF
data[5] = (outY >> 8) & 0xFF
data[6] = (outY >> 16) & 0xFF
data[7] = (outY >> 24) & 0xFF
@staticmethod
def __operatorA(off: int, state: int) -> int:
v3 = (state >> 32) & 0xFFFFFFFF
v4 = state & 0xFFFFFFFF
for i in range(0, 32, 4):
v20 = CardCipher.__ror(v3 ^ CardCipher.KEY[off + i + 1], 28)
v4 ^= (
CardCipher.LUT_B0[(v20 >> 26) & 0x3F] ^
CardCipher.LUT_B1[(v20 >> 18) & 0x3F] ^
CardCipher.LUT_B2[(v20 >> 10) & 0x3F] ^
CardCipher.LUT_B3[(v20 >> 2) & 0x3F] ^
CardCipher.LUT_A0[((v3 ^ CardCipher.KEY[off + i]) >> 26) & 0x3F] ^
CardCipher.LUT_A1[((v3 ^ CardCipher.KEY[off + i]) >> 18) & 0x3F] ^
CardCipher.LUT_A2[((v3 ^ CardCipher.KEY[off + i]) >> 10) & 0x3F] ^
CardCipher.LUT_A3[((v3 ^ CardCipher.KEY[off + i]) >> 2) & 0x3F]
)
v21 = CardCipher.__ror(v4 ^ CardCipher.KEY[off + i + 3], 28)
v3 ^= (
CardCipher.LUT_B0[(v21 >> 26) & 0x3F] ^
CardCipher.LUT_B1[(v21 >> 18) & 0x3F] ^
CardCipher.LUT_B2[(v21 >> 10) & 0x3F] ^
CardCipher.LUT_B3[(v21 >> 2) & 0x3F] ^
CardCipher.LUT_A0[((v4 ^ CardCipher.KEY[off + i + 2]) >> 26) & 0x3F] ^
CardCipher.LUT_A1[((v4 ^ CardCipher.KEY[off + i + 2]) >> 18) & 0x3F] ^
CardCipher.LUT_A2[((v4 ^ CardCipher.KEY[off + i + 2]) >> 10) & 0x3F] ^
CardCipher.LUT_A3[((v4 ^ CardCipher.KEY[off + i + 2]) >> 2) & 0x3F]
)
return ((v3 & 0xFFFFFFFF) << 32) | (v4 & 0xFFFFFFFF)
@staticmethod
def __operatorB(off: int, state: int) -> int:
v3 = (state >> 32) & 0xFFFFFFFF
v4 = state & 0xFFFFFFFF
for i in range(0, 32, 4):
v20 = CardCipher.__ror(v3 ^ CardCipher.KEY[off + 31 - i], 28)
v4 ^= (
CardCipher.LUT_A0[((v3 ^ CardCipher.KEY[off + 30 - i]) >> 26) & 0x3F] ^
CardCipher.LUT_A1[((v3 ^ CardCipher.KEY[off + 30 - i]) >> 18) & 0x3F] ^
CardCipher.LUT_A2[((v3 ^ CardCipher.KEY[off + 30 - i]) >> 10) & 0x3F] ^
CardCipher.LUT_A3[((v3 ^ CardCipher.KEY[off + 30 - i]) >> 2) & 0x3F] ^
CardCipher.LUT_B0[(v20 >> 26) & 0x3F] ^
CardCipher.LUT_B1[(v20 >> 18) & 0x3F] ^
CardCipher.LUT_B2[(v20 >> 10) & 0x3F] ^
CardCipher.LUT_B3[(v20 >> 2) & 0x3F]
)
v21 = CardCipher.__ror(v4 ^ CardCipher.KEY[off + 29 - i], 28)
v3 ^= (
CardCipher.LUT_A0[((v4 ^ CardCipher.KEY[off + 28 - i]) >> 26) & 0x3F] ^
CardCipher.LUT_A1[((v4 ^ CardCipher.KEY[off + 28 - i]) >> 18) & 0x3F] ^
CardCipher.LUT_A2[((v4 ^ CardCipher.KEY[off + 28 - i]) >> 10) & 0x3F] ^
CardCipher.LUT_A3[((v4 ^ CardCipher.KEY[off + 28 - i]) >> 2) & 0x3F] ^
CardCipher.LUT_B0[(v21 >> 26) & 0x3F] ^
CardCipher.LUT_B1[(v21 >> 18) & 0x3F] ^
CardCipher.LUT_B2[(v21 >> 10) & 0x3F] ^
CardCipher.LUT_B3[(v21 >> 2) & 0x3F]
)
return ((v3 & 0xFFFFFFFF) << 32) | (v4 & 0xFFFFFFFF)
@staticmethod
def __ror(val: int, amount: int) -> int:
return ((val << (32 - amount)) & 0xFFFFFFFF) | ((val >> amount) & 0xFFFFFFFF)