程序人生 A log of my life

Java

portable java

我喜欢portable的工具,所以jre也是,可以从openjdk下载zip包,展开jre,添加path即可。如果要注册jar的双击运行,用管理员权限运行下面的脚本即可:

assoc .jar=jarfile
ftype jarfile="%~dp0bin\javaw.exe" -jar "%%1" %%*

注意这个脚本是放在jre根目录下的,因为是脚本所以%需要转义。

Base64

base64算法在标准jdk8以后版本才有,之前jdk可以用commons-codec,安卓则很早版本就有了。按RFC标准,base64的encoder是可以每隔多少个字符输出一个回车,所以一定注意这个选项,可能导致不同平台的base64不兼容,比如安卓版本缺省就有这个回车选项,所以要加参数。

Base64.encodeToString(str, Base64.NO_WRAP)

MD5

public static String md5(String s, boolean lowercase, String def) {
    try {
        MessageDigest m = MessageDigest.getInstance("MD5");
        String hash = new BigInteger(1, m.digest(lowercase ? s.toLowerCase().getBytes("UTF-8") : s.getBytes("UTF-8"))).toString(16);
        //前面要补0
        while(hash.length() < 32 ){
            hash = "0" + hash;
        }
        return hash;
    } catch (Exception e) {
        return def;
    }
}

需要注意如果命令行使用linux工具来和上面算法校验,要用-ne参数,表示去除回车echo -ne test | md5sum

RSA加密

使用这个算法,在跨平台使用时,在Cipher.getInstance初始化时一定要指定所有选项,而不能仅仅是”RSA”,因为android和desktop可能细节的选项是不一样的,导致一端加密,另一端解不了。

public static String rsaEncrypt(String key, String src) throws Exception {
  byte[] keyBytes = Base64.decodeBase64(key.getBytes());
  PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
  PrivateKey privateKey = KeyFactory.getInstance("RSA").generatePrivate(keySpec);
  
  Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");  
  cipher.init(Cipher.ENCRYPT_MODE, privateKey);  

  return new String(Base64.encodeBase64(cipher.doFinal(src.getBytes("utf-8"))));
}

public static String rsaDecrypt(String key, String src) throws Exception {
  byte[] keyBytes = Base64.decodeBase64(key.getBytes());
  X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
  PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(spec);
  
  Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");  
  cipher.init(Cipher.DECRYPT_MODE, publicKey);

  return new String(cipher.doFinal(Base64.decodeBase64(src)),"utf-8");  		
  }

因为RSA的特性,输入的长度要小于等于key的长度,输出的长度等于key的长度,所以如果需要强度,需要选择key比较长,如果需要较短的输出,选择短的key,但最短只能是512bit,生成公钥和私钥对可以用下面的算法。

  KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
  //rsa的key至少512长度,为了加密输出最短,就用最小的512,但是能加密的最长字符也就只有53了
  keyGen.initialize(512);
  KeyPair pair = keyGen.genKeyPair();
  byte[] publicKey = pair.getPublic().getEncoded();
  byte[] privateKey = pair.getPrivate().getEncoded();
  
  System.out.println(Base64.encodeBase64String(publicKey));
  System.out.println(Base64.encodeBase64String(privateKey));

设置工具使用英文

通过环境变量来更改,Windows下可以这样设置:

set JAVA_TOOL_OPTIONS="-Duser.language=en"

maven 镜像

国内使用阿里云的maven镜像要快很多,设置方法时修改conf/settings.xml,添加

  <mirrors>
   <mirror>
    <id>aliyunmaven</id>
    <mirrorOf>*</mirrorOf>
    <name>阿里云公共仓库</name>
    <url>https://maven.aliyun.com/repository/public</url>
   </mirror>
  </mirrors>

对于有些使用maven wrapper的工具,可能有多个maven版本,可以把这个settings.xml拷贝到\.m2下面就可以了。

### http库 java自带的http api过于底层,一般可以使用apache HttpClient,但这个轻量的http-request非常不错值得一用。比如从anki上下载,需要先通过post取到302的下载地址,再下载文件,可以这样

  String addr = HttpRequest.post("https://ankiweb.net/shared/downloadDeck/627603312")
    			.form("k", "xxxkey")
    			.followRedirects(false)
    			.location();

  HttpRequest.get(addr).header("User-Agent","curl/7.55.1").receive(new File("output"));

json

org.json.JSONObject在导出json时,会把”/”字符转义,原因时考虑json有可能嵌入在html里,所以输出的json可能会出现”\/aaa\/bbb”这样的字符串。

反编译

java反编译有很多工具,jadx值得推荐,它可以直接打开apk进行反编译,gui也做的很好,除此之外的工具有:

  • 很多人推荐jd-gui,这个工具不能直接打开dex,只支持jar,反编译的效果和jadx互有胜负,。
  • dex-tools,用于将dex转换为jar,对于不支持直接反编译dex的工具,需要用这个先转换一下。
  • apk-tool 如果要看apk中的string资源,需要这个命令行工具。java -jar apktool.jar d xxx.apk就可以了
  • bytecodeviewer,这是个GUI工具,内置了三四种java反编译器,可以多个反编译器的效果对比,但是GUI做的很不好用。

bytecodeviewer支持的几种反编译器里proycon和fenflower都不错,比jadx/jd-gui效果要好。

重新打包

使用apktool可以重新打开一个apk,比如可以调整debug属性,backup属性,甚至改包名, 不过一般需要先通过apktool if安装厂商的资源包

apktool_2.5.0.jar if framework-res.apk
apktool_2.5.0.jar d xxx.apk
REM 修改xxx\AndroidManifest.xml和apktool.yml,可以在apktool.yml里修改renameManifestPackage
apktool_2.5.0.jar b xxx -o xxx2.apk
jarsigner.exe -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore yyy.keystore xxx2.apk yyy

break语言

java的break语句并不常用,但是在反编译的结果中却比较常见,break的意思是跳出当前scope,因此和goto是不一样的。

first:
for( int i = 0; i < 10; i++) {
  second:
  for(int j = 0; j < 5; j ++ )
  {
    break xxx; //xxx可以是first,也可以是second,但不能是third
  }
}

third:
for( int a = 0; a < 10; a++) {

}