package com.eu.habbo.crypto; import com.eu.habbo.crypto.exceptions.HabboCryptoException; import com.eu.habbo.crypto.utils.BigIntegerUtils; import org.apache.commons.lang3.mutable.MutableInt; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.math.BigInteger; import java.util.concurrent.ThreadLocalRandom; public class HabboRSACrypto { private final BigInteger e; private final BigInteger n; private final BigInteger d; private final int blockSize; public HabboRSACrypto(String e, String n) { this.e = new BigInteger(e, 16); this.n = new BigInteger(n, 16); this.d = null; this.blockSize = (this.n.bitLength() + 7) / 8; } public HabboRSACrypto(String e, String n, String d) { this.e = new BigInteger(e, 16); this.n = new BigInteger(n, 16); this.d = new BigInteger(d, 16); this.blockSize = (this.n.bitLength() + 7) / 8; } public byte[] Encrypt(byte[] data) throws HabboCryptoException { return DoEncrypt(data, true, 2); } public byte[] Decrypt(byte[] data) throws HabboCryptoException { return DoDecrypt(data, false, 2); } public byte[] Sign(byte[] data) throws HabboCryptoException { return DoEncrypt(data, false, 1); } public byte[] Verify(byte[] data) throws HabboCryptoException { return DoDecrypt(data, true, 1); } private BigInteger DoPublic(BigInteger x) { return x.modPow(this.e, this.n); } private BigInteger DoPrivate(BigInteger x) { return x.modPow(this.d, this.n); } private byte[] DoEncrypt(byte[] data, boolean isPublic, int padType) throws HabboCryptoException { try (ByteArrayOutputStream dst = new ByteArrayOutputStream()) { int bl = this.blockSize; int end = data.length; MutableInt pos = new MutableInt(0); while (pos.intValue() < end) { byte[] padded = Pkcs1Pad(data, pos, end, bl, padType); BigInteger block = new BigInteger(padded); BigInteger chunk = isPublic ? DoPublic(block) : DoPrivate(block); for (int b = (int) (bl - Math.ceil(chunk.bitLength() / 8.0)); b > 0; --b) { dst.write(0x00); } dst.write(BigIntegerUtils.toUnsignedByteArray(chunk)); } return dst.toByteArray(); } catch (IOException e) { throw new HabboCryptoException(e); } } private byte[] DoDecrypt(byte[] data, boolean isPublic, int padType) throws HabboCryptoException { if (data.length % this.blockSize != 0) { throw new HabboCryptoException("Decryption data was not in blocks of " + this.blockSize + " bytes, total " + data.length + "."); } try (ByteArrayOutputStream dst = new ByteArrayOutputStream()) { int end = data.length; int pos = 0; while (pos < end) { byte[] blockData = new byte[this.blockSize]; System.arraycopy(data, pos, blockData, 0, this.blockSize); BigInteger block = new BigInteger(1, blockData); BigInteger chunk = isPublic ? DoPublic(block) : DoPrivate(block); byte[] unpadded = Pkcs1Unpad(chunk.toByteArray(), this.blockSize, padType); pos += this.blockSize; dst.write(unpadded); } return dst.toByteArray(); } catch (IOException e) { throw new HabboCryptoException(e); } } private static byte[] Pkcs1Pad(byte[] src, MutableInt pos, int end, int n, int padType) { byte[] result = new byte[n]; int p = pos.intValue(); end = Math.min(end, Math.min(src.length, p + n - 11)); pos.setValue(end); int i = end - 1; while (i >= p && n > 11) { result[--n] = src[i--]; } result[--n] = 0; if (padType == 2) { while (n > 2) { result[--n] = (byte) ThreadLocalRandom.current().nextInt(1, 256); } } else { while (n > 2) { result[--n] = (byte) 0xFF; } } result[--n] = (byte) padType; result[--n] = 0; return result; } private static byte[] Pkcs1Unpad(byte[] b, int n, int padType) throws HabboCryptoException { byte[] result = new byte[n]; int resultPos = 0; int i = 0; while (i < b.length && b[i] == 0) { ++i; } if (b.length - i != n - 1 || b[i] != padType) { throw new HabboCryptoException("PKCS#1 unpad: i=" + i + ", expected b[i]==" + padType + ", got b[i]=" + b[i]); } ++i; while (b[i] != 0) { if (++i >= b.length) { throw new HabboCryptoException("PKCS#1 unpad: i=" + i + ", b[i-1]!=0 (=" + b[i-1] + ")"); } } while (++i < b.length) { result[resultPos++] = b[i]; } byte[] resultCopy = new byte[resultPos]; System.arraycopy(result, 0, resultCopy, 0, resultPos); return resultCopy; } }