diff --git a/src/main/java/com/buttongames/butterfly/Main.java b/src/main/java/com/buttongames/butterfly/Main.java index 054238c..8091338 100644 --- a/src/main/java/com/buttongames/butterfly/Main.java +++ b/src/main/java/com/buttongames/butterfly/Main.java @@ -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(); } } diff --git a/src/main/java/com/buttongames/butterfly/util/CardIdUtils.java b/src/main/java/com/buttongames/butterfly/util/CardIdUtils.java index 84f4c1a..e438124 100644 --- a/src/main/java/com/buttongames/butterfly/util/CardIdUtils.java +++ b/src/main/java/com/buttongames/butterfly/util/CardIdUtils.java @@ -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 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); } } diff --git a/src/main/java/com/buttongames/butterfly/util/CollectionUtils.java b/src/main/java/com/buttongames/butterfly/util/CollectionUtils.java index ba7bbdd..2d3d31c 100644 --- a/src/main/java/com/buttongames/butterfly/util/CollectionUtils.java +++ b/src/main/java/com/buttongames/butterfly/util/CollectionUtils.java @@ -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; + } } diff --git a/src/main/resources/cardconv.py b/src/main/resources/cardconv.py new file mode 100644 index 0000000..150617a --- /dev/null +++ b/src/main/resources/cardconv.py @@ -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) \ No newline at end of file