From 0a1addb2db219cbbbc9184be823bb1c069b9ae89 Mon Sep 17 00:00:00 2001 From: ecpvint Date: Thu, 15 Aug 2024 18:43:04 +0800 Subject: [PATCH] init --- .gitignore | 2 + app.iml | 119 +++++++++++ build.gradle | 21 ++ proguard-rules.pro | 21 ++ readme.md | 7 + .../ncmdecoder/ExampleInstrumentedTest.java | 26 +++ src/main/AndroidManifest.xml | 35 ++++ src/main/java/net/droidtech/io/DroidFile.java | 119 +++++++++++ .../java/net/droidtech/io/StreamReader.java | 44 ++++ .../java/net/droidtech/io/StreamWriter.java | 48 +++++ .../ncmdecoder/NCMDecoderThread.java | 189 ++++++++++++++++++ .../droidtech/ncmdecoder/NCMDecoderUI.java | 97 +++++++++ .../net/droidtech/ncmtypes/NCMApicBlock.java | 58 ++++++ .../net/droidtech/ncmtypes/NCMCrc32Block.java | 51 +++++ .../net/droidtech/ncmtypes/NCMKeyBlock.java | 88 ++++++++ .../droidtech/ncmtypes/NCMMetaInfoBlock.java | 67 +++++++ .../net/droidtech/ncmtypes/NCMTypeHeader.java | 48 +++++ .../java/net/droidtech/utils/AESUtils.java | 83 ++++++++ src/main/java/net/droidtech/utils/Base64.java | 116 +++++++++++ .../java/net/droidtech/utils/ByteMatcher.java | 33 +++ .../java/net/droidtech/utils/Formatter.java | 32 +++ .../net/droidtech/utils/HexStringUtils.java | 87 ++++++++ .../net/droidtech/utils/IntegerUtils.java | 51 +++++ src/main/java/net/droidtech/utils/RC4.java | 59 ++++++ src/main/res/drawable/ic_launcher.png | Bin 0 -> 10717 bytes .../res/layout/activity_ncmdecoder_ui.xml | 14 ++ src/main/res/values/strings.xml | 21 ++ .../droidtech/ncmdecoder/ExampleUnitTest.java | 17 ++ 28 files changed, 1553 insertions(+) create mode 100644 .gitignore create mode 100644 app.iml create mode 100644 build.gradle create mode 100644 proguard-rules.pro create mode 100644 readme.md create mode 100644 src/androidTest/java/net/droidtech/ncmdecoder/ExampleInstrumentedTest.java create mode 100644 src/main/AndroidManifest.xml create mode 100644 src/main/java/net/droidtech/io/DroidFile.java create mode 100644 src/main/java/net/droidtech/io/StreamReader.java create mode 100644 src/main/java/net/droidtech/io/StreamWriter.java create mode 100644 src/main/java/net/droidtech/ncmdecoder/NCMDecoderThread.java create mode 100644 src/main/java/net/droidtech/ncmdecoder/NCMDecoderUI.java create mode 100644 src/main/java/net/droidtech/ncmtypes/NCMApicBlock.java create mode 100644 src/main/java/net/droidtech/ncmtypes/NCMCrc32Block.java create mode 100644 src/main/java/net/droidtech/ncmtypes/NCMKeyBlock.java create mode 100644 src/main/java/net/droidtech/ncmtypes/NCMMetaInfoBlock.java create mode 100644 src/main/java/net/droidtech/ncmtypes/NCMTypeHeader.java create mode 100644 src/main/java/net/droidtech/utils/AESUtils.java create mode 100644 src/main/java/net/droidtech/utils/Base64.java create mode 100644 src/main/java/net/droidtech/utils/ByteMatcher.java create mode 100644 src/main/java/net/droidtech/utils/Formatter.java create mode 100644 src/main/java/net/droidtech/utils/HexStringUtils.java create mode 100644 src/main/java/net/droidtech/utils/IntegerUtils.java create mode 100644 src/main/java/net/droidtech/utils/RC4.java create mode 100644 src/main/res/drawable/ic_launcher.png create mode 100644 src/main/res/layout/activity_ncmdecoder_ui.xml create mode 100644 src/main/res/values/strings.xml create mode 100644 src/test/java/net/droidtech/ncmdecoder/ExampleUnitTest.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..956c004 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/build +/release \ No newline at end of file diff --git a/app.iml b/app.iml new file mode 100644 index 0000000..f8e37cb --- /dev/null +++ b/app.iml @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..3c652cb --- /dev/null +++ b/build.gradle @@ -0,0 +1,21 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 15 + defaultConfig { + applicationId "net.droidtech.ncmdecoder" + minSdkVersion 15 + versionCode 3 + versionName "1.0.6-beta" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) +} diff --git a/proguard-rules.pro b/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..800fb2e --- /dev/null +++ b/readme.md @@ -0,0 +1,7 @@ +## 影沐纪念项目 + +#### NCM解密 + +

解密网易音乐下载后的NCM加密

+ +

安卓项目

\ No newline at end of file diff --git a/src/androidTest/java/net/droidtech/ncmdecoder/ExampleInstrumentedTest.java b/src/androidTest/java/net/droidtech/ncmdecoder/ExampleInstrumentedTest.java new file mode 100644 index 0000000..18bb249 --- /dev/null +++ b/src/androidTest/java/net/droidtech/ncmdecoder/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package net.droidtech.ncmdecoder; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() throws Exception { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("net.droidtech.ncmdecoder", appContext.getPackageName()); + } +} diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml new file mode 100644 index 0000000..f838cf6 --- /dev/null +++ b/src/main/AndroidManifest.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/net/droidtech/io/DroidFile.java b/src/main/java/net/droidtech/io/DroidFile.java new file mode 100644 index 0000000..2a30ff4 --- /dev/null +++ b/src/main/java/net/droidtech/io/DroidFile.java @@ -0,0 +1,119 @@ +package net.droidtech.io; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.math.BigInteger; +import java.security.MessageDigest; + +public class DroidFile extends File{ + + private static final long serialVersionUID = 7311643678032690177L; + private File file=null; + + public DroidFile(String path){ + super(path); + file=new File(path); + } + public DataInputStream getInputStream(){ + try { + if(file.exists()&&!file.isDirectory()){ + DataInputStream in=new DataInputStream(new FileInputStream(file)); + return in; + }else{ + return null; + } + } catch (Exception e) { + // TODO Auto-generated catch block + return null; + } + + } + + public DataOutputStream getOutputStream(){ + try { + if(file.exists()&&!file.isDirectory()){ + DataOutputStream out=new DataOutputStream(new FileOutputStream(file)); + return out; + }else{ + return null; + } + } catch (Exception e) { + // TODO Auto-generated catch block + return null; + } + } + + public void copyTo(File target){ + DroidFile file=new DroidFile(target.getAbsolutePath()); + if(file.createNewFile()){ + StreamWriter writer=new StreamWriter(); + writer.writeStream(this.getInputStream(),file); + } + } + + @Override + public boolean createNewFile(){ + try { + File parentDir=new File(file.getParent()); + if(!parentDir.exists()){ + parentDir.mkdirs(); + } + file.createNewFile(); + } catch (IOException e) { + // TODO Auto-generated catch block + return false; + } + return file.exists(); + } + + @Override + public boolean mkdir(){ + file.mkdirs(); + return file.exists(); + } + + public String getMD5String(){ + byte[] md5data=getMD5Bytes(); + if(md5data!=null){ + return new BigInteger(1,md5data).toString(16); + }else{ + return null; + } + } + + public byte[] getMD5Bytes(){ + DataInputStream in=this.getInputStream(); + if(in==null){ + return null; + } + try { + byte[] buffer=new byte[4096]; + MessageDigest md5=MessageDigest.getInstance("MD5"); + while(true){ + int count=in.read(buffer); + if(count!=-1){ + md5.update(buffer,0,count); + }else{ + in.close(); + break; + } + } + return md5.digest(); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return null; + } + } + + public DroidFile getChildFile(String name){ + if(!this.isDirectory()){ + return null; + } + return new File(this.getAbsolutePath()+File.separator+name).exists()?new DroidFile(this.getAbsolutePath()+File.separator+name):null; + } +} diff --git a/src/main/java/net/droidtech/io/StreamReader.java b/src/main/java/net/droidtech/io/StreamReader.java new file mode 100644 index 0000000..9de3123 --- /dev/null +++ b/src/main/java/net/droidtech/io/StreamReader.java @@ -0,0 +1,44 @@ +package net.droidtech.io; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; + +public class StreamReader { + + public String readStream(InputStream in,String encoding){ + try { + return new String(readStream(in),encoding); + } catch (UnsupportedEncodingException e) { + throw new IllegalArgumentException("Undefined encoding:"+encoding); + } + + } + + public byte[] readStream(InputStream in){ + + if(in==null){ + return null; + } + + ByteArrayOutputStream data=new ByteArrayOutputStream(); + byte[] buffer=new byte[4096]; + + try{ + while(true){ + int count=in.read(buffer); + if(count!=-1){ + data.write(buffer,0,count); + }else{ + in.close(); + data.close(); + break; + } + } + return data.toByteArray(); + }catch(IOException e){ + return null; + } + } +} diff --git a/src/main/java/net/droidtech/io/StreamWriter.java b/src/main/java/net/droidtech/io/StreamWriter.java new file mode 100644 index 0000000..cc20853 --- /dev/null +++ b/src/main/java/net/droidtech/io/StreamWriter.java @@ -0,0 +1,48 @@ +package net.droidtech.io; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +public class StreamWriter { + + public void writeStream(InputStream in,File outFile){ + writeStream(in,outFile,false); + } + + public void writeStream(InputStream in,File outFile,boolean append){ + + byte[] buffer=new byte[4096]; + + if(in==null||outFile==null){ + return; + } + + try{ + + DataOutputStream out=new DataOutputStream(new FileOutputStream(outFile,append)); + DataInputStream dataInput=new DataInputStream(in); + + while(true){ + + int count=dataInput.read(buffer); + if(count!=-1){ + out.write(buffer,0,count); + }else{ + dataInput.close(); + out.close(); + break; + } + } + + }catch(IOException e){ + + throw new IllegalStateException(e.getMessage()); + + } + } + +} diff --git a/src/main/java/net/droidtech/ncmdecoder/NCMDecoderThread.java b/src/main/java/net/droidtech/ncmdecoder/NCMDecoderThread.java new file mode 100644 index 0000000..332a4a0 --- /dev/null +++ b/src/main/java/net/droidtech/ncmdecoder/NCMDecoderThread.java @@ -0,0 +1,189 @@ +package net.droidtech.ncmdecoder; + +import android.os.Handler; +import android.os.Message; + +import net.droidtech.io.DroidFile; +import net.droidtech.ncmtypes.NCMApicBlock; +import net.droidtech.ncmtypes.NCMCrc32Block; +import net.droidtech.ncmtypes.NCMKeyBlock; +import net.droidtech.ncmtypes.NCMMetaInfoBlock; +import net.droidtech.ncmtypes.NCMTypeHeader; +import net.droidtech.utils.Formatter; +import net.droidtech.utils.RC4; + +import org.json.JSONArray; +import org.json.JSONObject; + +import java.io.DataInputStream; +import java.io.DataOutputStream; + +/** + * Created by root on 2020/4/9. + */ + +public class NCMDecoderThread extends Thread { + + private Handler handler; + private String filePath; + private NCMDecoderUI ui; + + public void setHandler(Handler handler){ + + this.handler=handler; + + } + + public void attachUI(NCMDecoderUI ui){ + + this.ui=ui; + + } + + public void setSourceNCMFile(String filePath){ + + this.filePath=filePath; + + } + + private void writeLog(String log){ + + this.writeLog(log,0); + + } + + private void writeLog(String log, int level){ + + Message msg=handler.obtainMessage(); + + msg.obj=log; + + if(level!=0){ + msg.what=level; + } + + msg.sendToTarget(); + + } + + @Override + public void run(){ + + DroidFile ncmfile=new DroidFile(filePath); + + this.writeLog(ui.getStringById(R.string.target_file)+filePath); + + if(!ncmfile.exists()||!ncmfile.canRead()||ncmfile.isDirectory()){ + + this.writeLog(ui.getStringById(R.string.access_denied), NCMDecoderUI.LogHandler.RESULT_FAILED); + + return; + + } + + DataInputStream ncmstream=ncmfile.getInputStream(); + + NCMTypeHeader hdr=new NCMTypeHeader(ncmstream); + + this.writeLog(ui.getStringById(R.string.is_ncm_file)+(hdr.isNcmHeader()?ui.getStringById(R.string.yes):ui.getStringById(R.string.no))); + + if(!hdr.isNcmHeader()){ + + this.writeLog(ui.getStringById(R.string.not_an_ncm_file), NCMDecoderUI.LogHandler.RESULT_FAILED); + + return; + + } + + NCMKeyBlock keyblk=new NCMKeyBlock(ncmstream); + + this.writeLog(ui.getStringById(R.string.can_decrypt)+(keyblk.isSupportedKeyBlock()?ui.getStringById(R.string.yes):ui.getStringById(R.string.no))); + + if(!keyblk.isSupportedKeyBlock()){ + + this.writeLog(ui.getStringById(R.string.unsupported_ncm_file), NCMDecoderUI.LogHandler.RESULT_FAILED); + + return; + + } + + byte[] sbox= RC4.getSbox(keyblk.getRC4Key()); + + NCMMetaInfoBlock metainfo=new NCMMetaInfoBlock(ncmstream); + + //System.out.println("歌曲元数据: "+metainfo.getJsonMetaInfo()); + + NCMCrc32Block crc32=new NCMCrc32Block(ncmstream); + + this.writeLog(ui.getStringById(R.string.crc32_check_result)+ui.getStringById(R.string.unimplemented)); + + NCMApicBlock pic=new NCMApicBlock(ncmstream); + + try{ + + byte[] buffer=new byte[4096]; + + JSONObject json=new JSONObject(metainfo.getJsonMetaInfo()); + + String songName=Formatter.removeIllegalCharacters(json.getString("musicName")); + String artist=Formatter.removeIllegalCharacters(new JSONArray(new JSONArray(json.getString("artist")).getString(0)).getString(0)); + String format=json.getString("format"); + + if(pic.getPictureData()!=null){ + + this.writeLog(ui.getStringById(R.string.image_size)+pic.getPictureData().length+ui.getStringById(R.string.size_byte)); + + DroidFile outImg=new DroidFile(ncmfile.getParentFile().getAbsolutePath()+DroidFile.separator+songName+"_"+artist+"_pic.jpg"); + + outImg.createNewFile(); + + this.writeLog(ui.getStringById(R.string.writing_file)+outImg.getAbsolutePath()); + + DataOutputStream imgOutStream=outImg.getOutputStream(); + imgOutStream.write(pic.getPictureData()); + + imgOutStream.close(); + + }else{ + + this.writeLog(ui.getStringById(R.string.apic_not_found)); + + } + + DroidFile outFile=new DroidFile(ncmfile.getParentFile().getAbsolutePath()+DroidFile.separator+songName+"_"+artist+"."+format); + outFile.createNewFile(); + + DataOutputStream out=outFile.getOutputStream(); + + this.writeLog(ui.getStringById(R.string.writing_file)+outFile.getAbsolutePath()); + + while(true){ + + int length=ncmstream.read(buffer); + + if(length==-1){ + + break; + + } + + RC4.decrypt_rc4_prga(buffer,length,sbox); + + out.write(buffer,0, length); + + } + + ncmstream.close(); + out.close(); + + this.writeLog(ui.getStringById(R.string.decryption_success), NCMDecoderUI.LogHandler.RESULT_SUCCESS); + + }catch(Exception e){ + + this.writeLog(e.getMessage(), NCMDecoderUI.LogHandler.RESULT_FAILED); + + } + + } + +} diff --git a/src/main/java/net/droidtech/ncmdecoder/NCMDecoderUI.java b/src/main/java/net/droidtech/ncmdecoder/NCMDecoderUI.java new file mode 100644 index 0000000..8d94b90 --- /dev/null +++ b/src/main/java/net/droidtech/ncmdecoder/NCMDecoderUI.java @@ -0,0 +1,97 @@ +package net.droidtech.ncmdecoder; + +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.app.Activity; +import android.os.Environment; +import android.os.Handler; +import android.os.Message; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.TextView; + +public class NCMDecoderUI extends Activity { + + private LinearLayout layout; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_ncmdecoder_ui); + + Intent intent = getIntent(); + Uri uri = intent.getData(); + + if(uri==null){ + + this.finish(); + return; + + } + + String uriPath=uri.getPath(); + + if(uriPath.startsWith("/root")){ + + uriPath=uriPath.replace("/root",""); + + } + + this.setTitle(R.string.window_title); + + this.setFinishOnTouchOutside(false); + + this.layout=(LinearLayout)this.findViewById(R.id.logger); + + NCMDecoderThread thread=new NCMDecoderThread(); + + thread.setHandler(new LogHandler()); + thread.setSourceNCMFile(uriPath); + thread.attachUI(this); + + thread.start(); + + + } + + public String getStringById(int id){ + + return this.getResources().getString(id); + + } + + public class LogHandler extends Handler{ + + public static final int RESULT_FAILED=0xFF; + public static final int RESULT_SUCCESS=0xFC; + private ViewGroup.LayoutParams textViewLayout=new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.MATCH_PARENT); + + @Override + public void handleMessage(Message msg){ + + TextView message=new TextView(NCMDecoderUI.this); + + message.setText((CharSequence)msg.obj); + + message.setLayoutParams(textViewLayout); + + if(msg.what==LogHandler.RESULT_FAILED){ + + message.setTextColor(NCMDecoderUI.this.getResources().getColor(android.R.color.holo_red_light)); + + } + + if(msg.what==LogHandler.RESULT_SUCCESS){ + + message.setTextColor(NCMDecoderUI.this.getResources().getColor(android.R.color.holo_green_light)); + + } + + NCMDecoderUI.this.layout.addView(message); + + } + + } + +} diff --git a/src/main/java/net/droidtech/ncmtypes/NCMApicBlock.java b/src/main/java/net/droidtech/ncmtypes/NCMApicBlock.java new file mode 100644 index 0000000..92a38b7 --- /dev/null +++ b/src/main/java/net/droidtech/ncmtypes/NCMApicBlock.java @@ -0,0 +1,58 @@ +package net.droidtech.ncmtypes; + +import java.io.DataInputStream; + +import net.droidtech.utils.ByteMatcher; +import net.droidtech.utils.IntegerUtils; + +public class NCMApicBlock { + + public static final int APIC_BLOCK_START=1; + + private byte[] mPic; + + public NCMApicBlock(DataInputStream in){ + + try{ + + if(in.read()!=NCMApicBlock.APIC_BLOCK_START){ + + return; + + } + + byte[] magic_len=new byte[4]; + + in.readFully(magic_len); + + byte[] pic_len_data=new byte[4]; + + in.readFully(pic_len_data); + + if(!ByteMatcher.isMatch(magic_len, pic_len_data)){ + + return; + + } + + int pic_len=IntegerUtils.changeEndian(IntegerUtils.byteToInteger(pic_len_data)); + + this.mPic=new byte[pic_len]; + + in.readFully(this.mPic); + + }catch(Exception e){ + + e.printStackTrace(); + + } + + } + + public byte[] getPictureData(){ + + return this.mPic; + + } + +} diff --git a/src/main/java/net/droidtech/ncmtypes/NCMCrc32Block.java b/src/main/java/net/droidtech/ncmtypes/NCMCrc32Block.java new file mode 100644 index 0000000..71d21cf --- /dev/null +++ b/src/main/java/net/droidtech/ncmtypes/NCMCrc32Block.java @@ -0,0 +1,51 @@ +package net.droidtech.ncmtypes; + +import java.io.DataInputStream; + +import net.droidtech.utils.IntegerUtils; + +public class NCMCrc32Block { + + //unimplemented + + private boolean mIsCrc32Matched; + private byte[] mCrc32; + + public NCMCrc32Block(DataInputStream in){ + + try{ + + byte[] crc32=new byte[4]; + in.readFully(crc32); + + this.mCrc32=crc32; + + this.mIsCrc32Matched=true; + + }catch(Exception e){ + + e.printStackTrace(); + + } + + } + + public boolean isCrc32Matched(){ + + return this.mIsCrc32Matched; + + } + + public byte[] getCrc32Byte(){ + + return this.mCrc32; + + } + + public int getCrc32Int(){ + + return IntegerUtils.byteToInteger(this.mCrc32); + + } + +} diff --git a/src/main/java/net/droidtech/ncmtypes/NCMKeyBlock.java b/src/main/java/net/droidtech/ncmtypes/NCMKeyBlock.java new file mode 100644 index 0000000..c7345c2 --- /dev/null +++ b/src/main/java/net/droidtech/ncmtypes/NCMKeyBlock.java @@ -0,0 +1,88 @@ +package net.droidtech.ncmtypes; + +import java.io.DataInputStream; + +import net.droidtech.utils.AESUtils; +import net.droidtech.utils.ByteMatcher; +import net.droidtech.utils.HexStringUtils; +import net.droidtech.utils.IntegerUtils; + +public class NCMKeyBlock{ + + public static final byte[] KEY_BLOCK_MAGIC={0x01,0x70}; + + public static final int XOR_MAIN_KEY=0x64; + public static final String AES_MAIN_KEY="687A4852416D736F356B496E62617857"; + + public static final String KEY_PADDING="neteasecloudmusic"; + + private byte[] mKeyData=null; + private boolean mIsSupportedKeyBlock=false; + + public NCMKeyBlock(DataInputStream in){ + + try{ + + byte[] magic=new byte[NCMKeyBlock.KEY_BLOCK_MAGIC.length]; + + in.readFully(magic); + + /*if(!ByteMatcher.isMatch(NCMKeyBlock.KEY_BLOCK_MAGIC,magic)){ + + return; + + }*/ + + byte[] key_length_data=new byte[4]; + + in.readFully(key_length_data); + + int key_length=IntegerUtils.changeEndian(IntegerUtils.byteToInteger(key_length_data)); + + byte[] key=new byte[key_length]; + + in.readFully(key); + + for(int i=0;iNCMTypeHeader.NCM_HEADER_LENGTH){ + + return; + + } + + if(new String(ncm_hdr).equals(NCMTypeHeader.NCM_HEADER)){ + + this.mIsNcmFmt=true; + + } + + }catch(Exception e){ + + e.printStackTrace(); + + return; + + } + + } + + public boolean isNcmHeader(){ + + return this.mIsNcmFmt; + + } + +} diff --git a/src/main/java/net/droidtech/utils/AESUtils.java b/src/main/java/net/droidtech/utils/AESUtils.java new file mode 100644 index 0000000..8b22be2 --- /dev/null +++ b/src/main/java/net/droidtech/utils/AESUtils.java @@ -0,0 +1,83 @@ +package net.droidtech.utils; + +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.spec.SecretKeySpec; + +public class AESUtils { + + public static final int MODE_ENCRYPT=Cipher.ENCRYPT_MODE; + public static final int MODE_DECRYPT=Cipher.DECRYPT_MODE; + + public static final int DEFAULT_KEY_LENGTH=128; + + private SecretKeySpec keySpec; + private Cipher cipher; + + public void setKey(byte[] key){ + + if(key==null){ + return; + } + + if(key.length<16){ + return; + } + + this.keySpec = new SecretKeySpec(key, "AES"); + + } + + public static byte[] generateRandomKey(){ + + return AESUtils.generateRandomKey(256); + + } + + public static byte[] generateRandomKey(int length){ + + try { + KeyGenerator keyGenerator = KeyGenerator.getInstance("AES"); + keyGenerator.init(length); + return keyGenerator.generateKey().getEncoded(); + } catch (Exception e) { + return null; + } + + } + + public void setMode(int mode){ + + try { + this.cipher = Cipher.getInstance("AES/ECB/NoPadding"); + cipher.init(mode, this.keySpec); + } catch (Exception e) { + throw new java.lang.RuntimeException(e.getMessage()); + } + + } + + public byte[] encrypt(byte[] data){ + + try { + byte[] result=this.cipher.doFinal(data); + return result; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + + } + + public byte[] decrypt(byte[] data){ + + try { + byte[] result=this.cipher.doFinal(data); + return result; + } catch (Exception e) { + return null; + } + + } + +} diff --git a/src/main/java/net/droidtech/utils/Base64.java b/src/main/java/net/droidtech/utils/Base64.java new file mode 100644 index 0000000..e426068 --- /dev/null +++ b/src/main/java/net/droidtech/utils/Base64.java @@ -0,0 +1,116 @@ +package net.droidtech.utils; + +import java.io.ByteArrayOutputStream; + +public class Base64 { + + private Base64(){ + + + + } + + public static String encode(byte[] data){ + + char[] tbl = { + 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P', + 'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f', + 'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v', + 'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/' + }; + + StringBuilder buffer = new StringBuilder(); + + int pad = 0; + + for (int i = 0; i < data.length; i += 3) { + + int b = ((data[i] & 0xFF) << 16) & 0xFFFFFF; + if (i + 1 < data.length) { + b |= (data[i+1] & 0xFF) << 8; + } else { + pad++; + } + if (i + 2 < data.length) { + b |= (data[i+2] & 0xFF); + } else { + pad++; + } + + for (int j = 0; j < 4 - pad; j++) { + int c = (b & 0xFC0000) >> 18; + buffer.append(tbl[c]); + b <<= 6; + } + } + for (int j = 0; j < pad; j++) { + buffer.append("="); + } + + return buffer.toString(); + + } + + + public static byte[] decode(String data){ + + int[] tbl = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, + 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, + 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, + 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 + }; + + byte[] bytes = data.getBytes(); + + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + + for (int i = 0; i < bytes.length; ) { + + int b = 0; + if (tbl[bytes[i]] != -1) { + b = (tbl[bytes[i]] & 0xFF) << 18; + } + // skip unknown characters + else { + i++; + continue; + } + + int num = 0; + if (i + 1 < bytes.length && tbl[bytes[i+1]] != -1) { + b = b | ((tbl[bytes[i+1]] & 0xFF) << 12); + num++; + } + if (i + 2 < bytes.length && tbl[bytes[i+2]] != -1) { + b = b | ((tbl[bytes[i+2]] & 0xFF) << 6); + num++; + } + if (i + 3 < bytes.length && tbl[bytes[i+3]] != -1) { + b = b | (tbl[bytes[i+3]] & 0xFF); + num++; + } + + while (num > 0) { + int c = (b & 0xFF0000) >> 16; + buffer.write((char)c); + b <<= 8; + num--; + } + i += 4; + } + return buffer.toByteArray(); + } + +} diff --git a/src/main/java/net/droidtech/utils/ByteMatcher.java b/src/main/java/net/droidtech/utils/ByteMatcher.java new file mode 100644 index 0000000..4d20383 --- /dev/null +++ b/src/main/java/net/droidtech/utils/ByteMatcher.java @@ -0,0 +1,33 @@ +package net.droidtech.utils; + +public class ByteMatcher { + + private ByteMatcher(){ + + + + } + + public static boolean isMatch(byte[] src, byte[] dst){ + + if(src.length!=dst.length){ + + return false; + + } + + for(int i=0;i",":","|"}; + public static final String[] mReplaceTable={"\'","-","-","×","?","《","》",":","#"}; + + public static String removeIllegalCharacters(String arg){ + + String result=arg; + + for(int i=0;i2||items[i].length()<=0){ + throw new IllegalArgumentException("Unsupported string format"); + } + result.write(Integer.parseInt(items[i],16)); + } + + } + + return result.toByteArray(); + + } + + public static String byteToHexString(byte[] value){ + + return byteToHexString(value,null); + + } + + public static String byteToHexString(byte[] value, String separator){ + + StringBuffer result=new StringBuffer(); + + for(int i=0;i=0;i--){ + + result=result+(IntegerUtils.getUnsignedByte((byte)(var>>(i*8)))<<(24-i*8)); + + } + + return result; + + } + + public static int byteToInteger(byte[] data){ + + int result=0; + + if(data.length>4){ + + return 0; + + } + + for(int i=0;i<4;i++){ + + result=result+(IntegerUtils.getUnsignedByte(data[i])<<(24-i*8)); + + } + + return result; + + } + + public static int getUnsignedByte(byte var){ + + return var&0xFF; + + } + +} diff --git a/src/main/java/net/droidtech/utils/RC4.java b/src/main/java/net/droidtech/utils/RC4.java new file mode 100644 index 0000000..bbc076d --- /dev/null +++ b/src/main/java/net/droidtech/utils/RC4.java @@ -0,0 +1,59 @@ +package net.droidtech.utils; + +public class RC4 { + + private RC4(){ + + + } + + public static byte[] getSbox(byte[] key){ + + byte[] box=new byte[256]; + + for(int i=0;i=key.length){ + + key_offset=0; + + } + + box[i]=box[c]; + box[c]=(byte)swap; + + last_byte=c; + + } + + return box; + + } + + public static void decrypt_rc4_prga(byte[] data, int data_length, byte[] sbox){ + + for(int i=1;i<(data_length==0?(data.length+1):(data_length+1));i++){ + + int j = i & 0xff; + + data[i-1] ^= sbox[(sbox[j] + sbox[(sbox[j]+j)&0xff])&0xff]; + + } + + } + +} diff --git a/src/main/res/drawable/ic_launcher.png b/src/main/res/drawable/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..e3a29398f317b29acdebb47fa4d7e629b27d5b87 GIT binary patch literal 10717 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 P00000NkvXXu0mjfe6e>~ literal 0 HcmV?d00001 diff --git a/src/main/res/layout/activity_ncmdecoder_ui.xml b/src/main/res/layout/activity_ncmdecoder_ui.xml new file mode 100644 index 0000000..981bd31 --- /dev/null +++ b/src/main/res/layout/activity_ncmdecoder_ui.xml @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml new file mode 100644 index 0000000..59e426c --- /dev/null +++ b/src/main/res/values/strings.xml @@ -0,0 +1,21 @@ + + NCM 解码插件 + 日志 + 要解密的文件: + 解密失败: 目标文件有误,请确保插件有权访问该存储空间! + 是否为NCM格式: + 解密失败: 不是NCM格式! + 能否解密: + 解密失败: 不支持的加密方式! + CRC32检查结果: + (此功能未实现!) + 封面图像大小: + 字节 + 正在写入: + 未找到封面图片! + 解密完成! + + + + + diff --git a/src/test/java/net/droidtech/ncmdecoder/ExampleUnitTest.java b/src/test/java/net/droidtech/ncmdecoder/ExampleUnitTest.java new file mode 100644 index 0000000..ca6b673 --- /dev/null +++ b/src/test/java/net/droidtech/ncmdecoder/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package net.droidtech.ncmdecoder; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() throws Exception { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file