aboutsummaryrefslogtreecommitdiff
path: root/test-rig/Keygen.java
diff options
context:
space:
mode:
authorLena <lena@omega>2026-01-01 00:00:00 +0000
committerLena <lena@omega>2026-06-24 22:14:50 +0300
commiteb0c8951196c637e44daf3c0617b131b997d5d2c (patch)
tree2f83b6a41cee745467e53fc401942b82cea286ea /test-rig/Keygen.java
downloadscrcpy-android-0.1.tar.gz
scrcpy-android: mirror an Android device over wireless ADB0.1
Native Java app for Android 12+ that mirrors another Android device over wireless ADB, forwarding video, audio, touch input, and clipboard. Bundles a pinned scrcpy-server.jar and the vendored libadb-android stack. Supports h264/h265/av1 video and raw/opus audio with in-app codec selection. No NDK, no Kotlin. Includes JVM unit tests and a Docker-based emulator e2e rig.
Diffstat (limited to 'test-rig/Keygen.java')
-rw-r--r--test-rig/Keygen.java92
1 files changed, 92 insertions, 0 deletions
diff --git a/test-rig/Keygen.java b/test-rig/Keygen.java
new file mode 100644
index 0000000..8cb90a6
--- /dev/null
+++ b/test-rig/Keygen.java
@@ -0,0 +1,92 @@
+// Read a DER-encoded PKCS#8 RSA private key, derive the public key,
+// emit the Android adb_keys line on stdout.
+//
+// java test-rig/Keygen.java <path/to/adbkey.der>
+//
+// The Android pubkey blob format (from system/core/libcrypto_utils):
+// uint32_t modulus_size_words; // = 64 for RSA-2048
+// uint32_t n0inv; // -1 / N[0] mod 2^32
+// uint8_t modulus[256]; // little-endian
+// uint8_t rr[256]; // (2^2048)^2 mod N, little-endian
+// uint32_t exponent; // typically 65537
+// = 524 bytes, then base64-encoded, then " scrcpy-android@test".
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.KeyFactory;
+import java.security.PrivateKey;
+import java.security.interfaces.RSAPrivateCrtKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.RSAPublicKeySpec;
+import java.util.Base64;
+
+public final class Keygen {
+
+ private static final int MODULUS_SIZE = 256; // bytes (2048 / 8)
+ private static final int MODULUS_SIZE_WORDS = MODULUS_SIZE / 4;
+ private static final int ENCODED_SIZE = 3 * 4 + 2 * MODULUS_SIZE;
+
+ public static void main(String[] args) throws Exception {
+ if (args.length != 1) {
+ System.err.println("usage: java Keygen.java <der private key path>");
+ System.exit(2);
+ }
+ byte[] der = Files.readAllBytes(Path.of(args[0]));
+ PrivateKey priv = KeyFactory.getInstance("RSA")
+ .generatePrivate(new PKCS8EncodedKeySpec(der));
+ RSAPublicKey pub = derivePublic(priv);
+
+ byte[] blob = encode(pub);
+ String b64 = Base64.getEncoder().encodeToString(blob);
+ System.out.print(b64);
+ System.out.println(" scrcpy-android@test");
+ }
+
+ private static RSAPublicKey derivePublic(PrivateKey priv) throws Exception {
+ if (priv instanceof RSAPrivateCrtKey crt) {
+ RSAPublicKeySpec spec = new RSAPublicKeySpec(crt.getModulus(), crt.getPublicExponent());
+ return (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(spec);
+ }
+ throw new IOException("private key is not an RSAPrivateCrtKey");
+ }
+
+ private static byte[] encode(RSAPublicKey pub) {
+ ByteBuffer buf = ByteBuffer.allocate(ENCODED_SIZE).order(ByteOrder.LITTLE_ENDIAN);
+ BigInteger n = pub.getModulus();
+
+ buf.putInt(MODULUS_SIZE_WORDS);
+
+ // n0inv = (2^32) - (N[0] mod 2^32)^-1 mod 2^32
+ BigInteger r32 = BigInteger.ZERO.setBit(32);
+ BigInteger n0inv = n.mod(r32).modInverse(r32);
+ n0inv = r32.subtract(n0inv);
+ buf.putInt(n0inv.intValue());
+
+ buf.put(beToLe(MODULUS_SIZE, n));
+
+ // rr = (2^(MODULUS_SIZE*8))^2 mod N
+ BigInteger rr = BigInteger.ZERO.setBit(MODULUS_SIZE * 8).modPow(BigInteger.TWO, n);
+ buf.put(beToLe(MODULUS_SIZE, rr));
+
+ buf.putInt(pub.getPublicExponent().intValue());
+ return buf.array();
+ }
+
+ private static byte[] beToLe(int len, BigInteger v) {
+ byte[] be = v.toByteArray();
+ // strip leading sign byte if present
+ int start = 0;
+ if (be.length > len) start = be.length - len;
+ byte[] out = new byte[len];
+ for (int i = 0; i < len; i++) {
+ int srcIdx = be.length - 1 - i;
+ if (srcIdx >= start) out[i] = be[srcIdx];
+ }
+ return out;
+ }
+}