mirror of
https://github.com/real-zony/ZonyLrcToolsX.git
synced 2025-07-02 05:10:42 +00:00
feat: Completed the NCM decryption tool class.
完成 NCM 解密工具类。
This commit is contained in:
parent
fb64e46022
commit
def1de8467
@ -0,0 +1,16 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ZonyLrcTools.Cli.Infrastructure.MusicDecryption
|
||||
{
|
||||
public class DecryptionResult
|
||||
{
|
||||
public byte[] Data { get; protected set; }
|
||||
|
||||
public Dictionary<string, object> ExtensionObjects { get; set; }
|
||||
|
||||
public DecryptionResult(byte[] data)
|
||||
{
|
||||
Data = data;
|
||||
}
|
||||
}
|
||||
}
|
@ -12,6 +12,6 @@ namespace ZonyLrcTools.Cli.Infrastructure.MusicDecryption
|
||||
/// </summary>
|
||||
/// <param name="sourceBytes">源加密的歌曲数据。</param>
|
||||
/// <returns>解密完成的歌曲数据。</returns>
|
||||
Task<byte[]> Convert(byte[] sourceBytes);
|
||||
Task<DecryptionResult> ConvertMusic(byte[] sourceBytes);
|
||||
}
|
||||
}
|
@ -1,10 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using ZonyLrcTools.Cli.Infrastructure.DependencyInject;
|
||||
using ZonyLrcTools.Cli.Infrastructure.Exceptions;
|
||||
|
||||
namespace ZonyLrcTools.Cli.Infrastructure.MusicDecryption
|
||||
{
|
||||
@ -16,7 +17,7 @@ namespace ZonyLrcTools.Cli.Infrastructure.MusicDecryption
|
||||
protected readonly byte[] AesCoreKey = {0x68, 0x7A, 0x48, 0x52, 0x41, 0x6D, 0x73, 0x6F, 0x35, 0x6B, 0x49, 0x6E, 0x62, 0x61, 0x78, 0x57};
|
||||
protected readonly byte[] AesModifyKey = {0x23, 0x31, 0x34, 0x6C, 0x6A, 0x6B, 0x5F, 0x21, 0x5C, 0x5D, 0x26, 0x30, 0x55, 0x3C, 0x27, 0x28};
|
||||
|
||||
public Task<byte[]> Convert(byte[] sourceBytes)
|
||||
public async Task<DecryptionResult> ConvertMusic(byte[] sourceBytes)
|
||||
{
|
||||
var stream = new MemoryStream(sourceBytes);
|
||||
var streamReader = new BinaryReader(stream);
|
||||
@ -46,7 +47,7 @@ namespace ZonyLrcTools.Cli.Infrastructure.MusicDecryption
|
||||
keyBytes[i] ^= 0x64;
|
||||
}
|
||||
|
||||
var deKeyDataBytes = GetBytesByOffset(DecryptAes128Ecb(AesCoreKey, keyBytes), 17);
|
||||
var coreKeyBytes = GetBytesByOffset(DecryptAes128Ecb(AesCoreKey, keyBytes), 17);
|
||||
|
||||
var modifyDataBytes = new byte[streamReader.ReadInt32()];
|
||||
stream.Read(modifyDataBytes);
|
||||
@ -55,7 +56,25 @@ namespace ZonyLrcTools.Cli.Infrastructure.MusicDecryption
|
||||
modifyDataBytes[i] ^= 0x63;
|
||||
}
|
||||
|
||||
throw new System.NotImplementedException();
|
||||
var decryptBase64Bytes = Convert.FromBase64String(Encoding.UTF8.GetString(GetBytesByOffset(modifyDataBytes, 22)));
|
||||
var decryptModifyData = DecryptAes128Ecb(AesModifyKey, decryptBase64Bytes);
|
||||
|
||||
var musicInfoJson = JObject.Parse(Encoding.UTF8.GetString(GetBytesByOffset(decryptModifyData, 6)));
|
||||
|
||||
// CRC 校验
|
||||
stream.Seek(4, SeekOrigin.Current);
|
||||
stream.Seek(5, SeekOrigin.Current);
|
||||
|
||||
GetAlbumImageBytes(stream, streamReader);
|
||||
|
||||
var sBox = BuildKeyBox(coreKeyBytes);
|
||||
return new DecryptionResult(GetMusicBytes(sBox, stream).ToArray())
|
||||
{
|
||||
ExtensionObjects = new Dictionary<string, object>
|
||||
{
|
||||
{"JSON", musicInfoJson}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private byte[] GetBytesByOffset(byte[] srcBytes, int offset = 0)
|
||||
@ -75,5 +94,91 @@ namespace ZonyLrcTools.Cli.Infrastructure.MusicDecryption
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// RC4 加密,生成 KeyBox。
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <returns></returns>
|
||||
private byte[] BuildKeyBox(byte[] key)
|
||||
{
|
||||
byte[] box = new byte[256];
|
||||
for (int i = 0; i < 256; ++i)
|
||||
{
|
||||
box[i] = (byte) i;
|
||||
}
|
||||
|
||||
byte keyLength = (byte) key.Length;
|
||||
byte c;
|
||||
byte lastByte = 0;
|
||||
byte keyOffset = 0;
|
||||
byte swap;
|
||||
|
||||
for (int i = 0; i < 256; ++i)
|
||||
{
|
||||
swap = box[i];
|
||||
c = (byte) ((swap + lastByte + key[keyOffset++]) & 0xff);
|
||||
|
||||
if (keyOffset >= keyLength)
|
||||
{
|
||||
keyOffset = 0;
|
||||
}
|
||||
|
||||
box[i] = box[c];
|
||||
box[c] = swap;
|
||||
lastByte = c;
|
||||
}
|
||||
|
||||
return box;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获得歌曲的专辑图像信息。
|
||||
/// </summary>
|
||||
/// <param name="stream">原始文件流。</param>
|
||||
/// <param name="streamReader">二进制读取器。</param>
|
||||
private byte[] GetAlbumImageBytes(Stream stream, BinaryReader streamReader)
|
||||
{
|
||||
var imgLength = streamReader.ReadInt32();
|
||||
|
||||
if (imgLength <= 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var imgBuffer = streamReader.ReadBytes(imgLength);
|
||||
|
||||
return imgBuffer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获得歌曲的完整数据。
|
||||
/// </summary>
|
||||
/// <param name="sBox"></param>
|
||||
/// <param name="stream">原始文件流。</param>
|
||||
private MemoryStream GetMusicBytes(byte[] sBox, Stream stream)
|
||||
{
|
||||
var n = 0x8000;
|
||||
var memoryStream = new MemoryStream();
|
||||
|
||||
while (true)
|
||||
{
|
||||
var tb = new byte[n];
|
||||
var result = stream.Read(tb);
|
||||
if (result <= 0) break;
|
||||
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
var j = (byte) ((i + 1) & 0xff);
|
||||
tb[i] ^= sBox[sBox[j] + sBox[(sBox[j] + j) & 0xff] & 0xff];
|
||||
}
|
||||
|
||||
memoryStream.Write(tb);
|
||||
}
|
||||
|
||||
memoryStream.Flush();
|
||||
|
||||
return memoryStream;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Xunit;
|
||||
using ZonyLrcTools.Cli.Infrastructure.MusicDecryption;
|
||||
|
||||
namespace ZonyLrcTools.Tests.Infrastructure.MusicDecryption
|
||||
{
|
||||
public class NcmMusicDecryptorTests : TestBase
|
||||
{
|
||||
[Fact]
|
||||
public async Task ConvertMusic_Test()
|
||||
{
|
||||
var decryptor = ServiceProvider.GetRequiredService<IMusicDecryptor>();
|
||||
|
||||
await using var fs = File.Open(Path.Combine(Directory.GetCurrentDirectory(), "MusicFiles", "Loren Gray - Queen.ncm"), FileMode.Open);
|
||||
using var reader = new BinaryReader(fs);
|
||||
var response = await decryptor.ConvertMusic(reader.ReadBytes((int) fs.Length));
|
||||
|
||||
var musicFilePath = Path.Combine(Directory.GetCurrentDirectory(),
|
||||
"MusicFiles",
|
||||
$"Loren Gray - Queen.{((JObject) response.ExtensionObjects["JSON"]).SelectToken("$.format").Value<string>()}");
|
||||
|
||||
if (File.Exists(musicFilePath))
|
||||
{
|
||||
File.Delete(musicFilePath);
|
||||
}
|
||||
|
||||
await using var musicFileStream = File.Create(musicFilePath);
|
||||
await musicFileStream.WriteAsync(response.Data);
|
||||
await musicFileStream.FlushAsync();
|
||||
}
|
||||
}
|
||||
}
|
BIN
tests/ZonyLrcTools.Tests/MusicFiles/Loren Gray - Queen.ncm
Normal file
BIN
tests/ZonyLrcTools.Tests/MusicFiles/Loren Gray - Queen.ncm
Normal file
Binary file not shown.
@ -29,6 +29,10 @@
|
||||
<Content Include="MusicFiles\曾经艺也 - 荀彧(纯音乐版).mp3">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<None Remove="MusicFiles\Loren Gray - Queen.ncm" />
|
||||
<Content Include="MusicFiles\Loren Gray - Queen.ncm">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
Loading…
x
Reference in New Issue
Block a user