暗号化・復号化モジュール(TWLBackupBlock)を追加

git-svn-id: file:///Volumes/Transfer/gigaleak_20231201/2020-09-30%20-%20paladin.7z/paladin/ctr_test_tools@4 6b0af911-cb57-b745-895f-eec5701120e1
This commit is contained in:
N2891 2011-08-08 03:00:09 +00:00
parent a792749610
commit 717ff59ebb
19 changed files with 1907 additions and 0 deletions

View File

@ -0,0 +1,20 @@

Microsoft Visual Studio Solution File, Format Version 10.00
# Visual C# Express 2008
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TwlBackupBlock", "TWLBackupBlock\TwlBackupBlock.csproj", "{E62F6169-868A-48E4-9B1D-C2EB3BE11F39}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{E62F6169-868A-48E4-9B1D-C2EB3BE11F39}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E62F6169-868A-48E4-9B1D-C2EB3BE11F39}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E62F6169-868A-48E4-9B1D-C2EB3BE11F39}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E62F6169-868A-48E4-9B1D-C2EB3BE11F39}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<VisualState xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" ShowCheckBoxes="false">
<TopNode>[0-1000]C:\Users\N2891\Documents\Visual Studio 2008\Projects\TWLBackupBlock\TWLBackupBlock.sln</TopNode>
<SelectedNode>[0-1004]TwlBackupBlockTest.AesCmacTest.GetMacTest</SelectedNode>
<ExcludeCategories>false</ExcludeCategories>
<Nodes>
<Node UniqueName="[0-1000]C:\Users\N2891\Documents\Visual Studio 2008\Projects\TWLBackupBlock\TWLBackupBlock.sln" Expanded="true" />
<Node UniqueName="[0-1009]C:\Users\N2891\Documents\Visual Studio 2008\Projects\TWLBackupBlock\TwlBackupBlockTest\bin\Debug\TwlBackupBlockTest.dll" Expanded="true" />
<Node UniqueName="[0-1010]TwlBackupBlockTest" Expanded="true" />
<Node UniqueName="[0-1003]TwlBackupBlockTest.AesCmacTest" Expanded="true" />
<Node UniqueName="[0-1004]TwlBackupBlockTest.AesCmacTest.GetMacTest" Expanded="true" />
</Nodes>
</VisualState>

Binary file not shown.

View File

@ -0,0 +1,70 @@
using System;
using System.Collections;
using System.Collections.Generic;
namespace TwlBackupBlock
{
/// <summary>
/// データ本体を表す抽象クラスです。
/// </summary>
public abstract class AbstractBody : IEnumerable<byte>, ICloneable
{
/// <summary>
/// 指定したインデックスにアクセスします。
/// </summary>
/// <param name="index">アクセスするインデックス。</param>
/// <returns>指定したインデックスのバイトデータ。</returns>
public abstract byte this[int index]
{
get;
set;
}
/// <summary>
/// データ本体のバイトサイズを取得します。
/// </summary>
public abstract int Length
{
get;
}
/// <summary>
/// イテレータブロック。
/// </summary>
/// <returns>列挙子。</returns>
public IEnumerator<byte> GetEnumerator()
{
for (int i = 0; i < Length; i++)
{
yield return this[i];
}
}
/// <summary>
/// イテレータブロック。
/// </summary>
/// <returns>列挙子。</returns>
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
/// <summary>
/// データ本体を表すバイト配列を取得します。
/// </summary>
/// <returns>データ本体を表すバイト配列</returns>
public abstract byte[] GetBytes();
/// <summary>
/// バイト配列からデータ本体を構成します。
/// </summary>
/// <param name="bytes">データ本体を表すバイト配列</param>
public abstract void SetBytes(byte[] bytes);
/// <summary>
/// AbstractBodyのディープコピーを作成します。
/// </summary>
/// <returns>AbstractBodyのコピー</returns>
public abstract object Clone();
}
}

View File

@ -0,0 +1,170 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
namespace TwlBackupBlock
{
/// <summary>
/// AES-CMACによるMACを生成します。
/// 鍵は16バイト、24バイト、32バイトに対応しています。
/// </summary>
public class AesCmac
{
private const int BLOCK_SIZE = 16;
private static readonly int[] LEGAL_KEY_SIZES = { 16, 24, 32 };
/// <summary>
/// 指定された鍵を利用してデータのMACを取得します。
/// </summary>
/// <param name="data">MACを発行したいデータ。</param>
/// <param name="key">AES-CMACの鍵。</param>
/// <returns><paramref name="data"/>に対するMAC。</returns>
public static byte[] GetMac(byte[] data, byte[] key)
{
if (data == null)
{
throw new ArgumentNullException("data");
}
if (key == null)
{
throw new ArgumentNullException("key");
}
if (!CheckKeySize(key.Length))
{
string message = string.Format("key.Length is illegal (key.Length:{0})", key.Length);
throw new ArgumentException("key", message);
}
var context = new AesCmacContext(block => Encrypt(block, key));
context.Update(data);
return context.GetMac();
}
/// <summary>
/// 指定された鍵を利用して16バイトブロックをAESで暗号化します。
/// </summary>
/// <param name="block">暗号化するブロック。メソッド呼出し後は、暗号化されたブロック。</param>
/// <param name="key">AESの鍵。</param>
private static void Encrypt(byte[] block, byte[] key)
{
Debug.Assert(block.Length == BLOCK_SIZE);
Debug.Assert(CheckKeySize(key.Length));
using (var ms = new MemoryStream(block))
{
var aes = new RijndaelManaged();
aes.BlockSize = BLOCK_SIZE * 8;
aes.Mode = CipherMode.ECB;
aes.Key = key;
using (CryptoStream cs = new CryptoStream(ms, aes.CreateEncryptor(), CryptoStreamMode.Read))
{
cs.Read(block, 0, block.Length);
}
}
}
/// <summary>
/// 鍵のサイズが16バイト、24バイト、32バイトのいずれであるか判定します。
/// </summary>
/// <param name="keySize">鍵のバイトサイズ</param>
/// <returns></returns>
private static bool CheckKeySize(int keySize)
{
return LEGAL_KEY_SIZES.Any(e => (e == keySize));
}
/// <summary>
/// AES-CMAC生成器です。データの入力履歴に応じたMAC生成器の内部状態を保持します。
/// </summary>
private class AesCmacContext
{
public delegate void Encrypt(byte[] block);
private Encrypt encryptOperation;
private byte[] block;
private int filledIndex;
/// <summary>
/// 指定された暗号化処理をもとに、AesCmacContextクラスの新しいインスタンスを初期化します。
/// </summary>
/// <param name="op">16バイトブロックを暗号化するメソッド</param>
public AesCmacContext(Encrypt op)
{
encryptOperation = op;
block = Enumerable.Repeat<byte>(0, BLOCK_SIZE).ToArray();
filledIndex = 0;
}
/// <summary>
/// 指定された入力データによりMAC生成器を更新します。
/// </summary>
/// <param name="data">MACを発行したいデータ</param>
public void Update(byte[] data)
{
for (int i = 0; i < data.Length; )
{
if (filledIndex >= BLOCK_SIZE)
{
encryptOperation(block);
filledIndex = 0;
}
while (filledIndex < BLOCK_SIZE && i < data.Length)
{
block[filledIndex] ^= data[i];
++filledIndex;
++i;
}
}
}
/// <summary>
/// 現在までの入力履歴からMACを生成します。
/// </summary>
/// <returns>これまでの入力データに対するMAC</returns>
public byte[] GetMac()
{
byte[] padding = Enumerable.Repeat<byte>(0, BLOCK_SIZE).ToArray();
encryptOperation(padding);
Multiply128(padding);
if (filledIndex < BLOCK_SIZE)
{
block[filledIndex] ^= 0x80;
Multiply128(padding);
}
for (int i = 0; i < BLOCK_SIZE; ++i)
{
padding[i] ^= block[i];
}
encryptOperation(padding);
return padding;
}
/// <summary>
/// 16バイトの整数に128を乗じます。オーバーフローは無視します。
/// </summary>
/// <param name="block">16バイト整数</param>
private void Multiply128(byte[] block)
{
bool isCarried = ((block[0] & 0x80) != 0);
for (int i = 0; i < block.Length - 1; i++)
{
block[i] = (byte)(((uint)block[i] << 1) | ((uint)block[i + 1] >> 7));
}
block[block.Length - 1] <<= 1;
if (isCarried)
{
block[block.Length - 1] = (byte)(block[block.Length - 1] ^ 0x87);
}
}
}
}
}

View File

@ -0,0 +1,53 @@
using System;
using System.Diagnostics;
namespace TwlBackupBlock
{
/// <summary>
/// データブロックです。
/// </summary>
public class Block : ICloneable
{
private const int MAC_SIZE = 16;
private const int IV_SIZE = 16;
public AbstractBody body;
public byte[] mac;
public byte[] iv;
/// <summary>
/// Blockクラスの新しいインスタンスを初期化します。
/// </summary>
public Block()
{
body = null;
mac = null;
iv = null;
}
/// <summary>
/// 指定したデータ本体、MAC、IVを使用して、Blockクラスの新しいインスタンスを初期化します。
/// </summary>
/// <param name="body">データ本体。</param>
/// <param name="mac">MAC。</param>
/// <param name="iv">IV。</param>
public Block(AbstractBody body, byte[] mac, byte[] iv)
{
Debug.Assert(mac.Length == MAC_SIZE);
Debug.Assert(iv.Length == IV_SIZE);
this.body = body;
this.mac = mac;
this.iv = iv;
}
/// <summary>
/// Blockのディープコピーを作成します。
/// </summary>
/// <returns></returns>
public object Clone()
{
return new Block((AbstractBody)body.Clone(), (byte[])mac.Clone(), (byte[])iv.Clone());
}
}
}

View File

@ -0,0 +1,88 @@
using System;
using System.Diagnostics;
namespace TwlBackupBlock
{
/// <summary>
/// ブロックの集合です。
/// </summary>
public class Blocks : ICloneable
{
private const int NUM_BLOCKS = 7;
public Block banner;
public Block header;
public Block signature;
public Block tmd;
public Block content;
public Block saveData;
public Block subBanner;
/// <summary>
/// Blocksクラスの新しいインスタンスを初期化します。
/// </summary>
public Blocks()
{
banner = new Block();
header = new Block();
signature = new Block();
tmd = new Block();
content = new Block();
saveData = new Block();
subBanner = new Block();
}
/// <summary>
/// 指定したインデックスのブロックにアクセスします。
/// </summary>
/// <param name="index">インデックス。</param>
/// <returns>指定したインデックスのブロック。</returns>
public Block this[int index]
{
get
{
switch (index)
{
case 0: return banner;
case 1: return header;
case 2: return signature;
case 3: return tmd;
case 4: return content;
case 5: return saveData;
case 6: return subBanner;
default:
Debug.Assert(false);
return null; // never reach
}
}
}
/// <summary>
/// ブロック数(=7を取得します。
/// </summary>
public int Length
{
get
{
return NUM_BLOCKS;
}
}
/// <summary>
/// Blocksのディープコピーを作成します。
/// </summary>
/// <returns>Blocksのコピー</returns>
public object Clone()
{
Blocks newBlocks = new Blocks();
newBlocks.banner = (Block)banner.Clone();
newBlocks.header = (Block)header.Clone();
newBlocks.signature = (Block)signature.Clone();
newBlocks.tmd = (Block)tmd.Clone();
newBlocks.content = (Block)content.Clone();
newBlocks.saveData = (Block)saveData.Clone();
newBlocks.subBanner = (Block)subBanner.Clone();
return newBlocks;
}
}
}

View File

@ -0,0 +1,81 @@
using System;
using System.Diagnostics;
namespace TwlBackupBlock
{
/// <summary>
/// バイト単位でアクセスする機能だけを持ったデータ本体です。
/// </summary>
public class Body : AbstractBody
{
private byte[] data;
/// <summary>
/// 指定したデータ本体をもとに、Bodyクラスの新しいインスタンスを初期化します。
/// </summary>
/// <param name="data"></param>
public Body(byte[] data)
{
if (data == null)
{
throw new ArgumentNullException("data");
}
this.data = (byte[])data.Clone();
}
public override byte this[int index]
{
get
{
if (index < 0 || index >= data.Length)
{
throw new ArgumentOutOfRangeException("index");
}
return data[index];
}
set
{
if (index < 0 || index >= data.Length)
{
throw new ArgumentOutOfRangeException("index");
}
data[index] = value;
}
}
public override int Length
{
get
{
Debug.Assert(data != null);
return data.Length;
}
}
public override byte[] GetBytes()
{
Debug.Assert(data != null);
return (byte[])data.Clone();
}
public override void SetBytes(byte[] bytes)
{
Debug.Assert(data != null);
if (bytes == null)
{
throw new ArgumentNullException("bytes");
}
if (bytes.Length != data.Length)
{
string message = string.Format("bytes.Length != {0} (bytes.Length:{1})", data.Length, bytes.Length);
throw new ArgumentException("bytes", message);
}
bytes.CopyTo(data, 0);
}
public override object Clone()
{
return new Body(GetBytes());
}
}
}

View File

@ -0,0 +1,138 @@
using System;
using System.IO;
namespace TwlBackupBlock
{
/// <summary>
/// BinaryReaderのReadをラップするクラスです。
/// </summary>
public class ExtBinaryReader : IDisposable
{
private bool disposed;
private BinaryReader br;
/// <summary>
/// 指定したMemoryStreamをもとに、ExtBinaryReaderクラスの新しいインスタンスを初期化します。
/// </summary>
/// <param name="ms"></param>
public ExtBinaryReader(MemoryStream ms)
{
disposed = false;
br = new BinaryReader(ms);
}
~ExtBinaryReader()
{
Dispose(false);
}
/// <summary>
/// ExtBinaryReaderを破棄します。
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// ExtBinaryReaderを破棄します。
/// </summary>
/// <param name="disposing">BinaryReaderも破棄する場合はtrue。それ以外の場合はfalse。</param>
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
((IDisposable)br).Dispose();
}
}
disposed = true;
}
/// <summary>
/// 現在のストリームから次のバイトを読み取り、ストリームの現在位置を1バイトだけ進めます。
/// </summary>
/// <param name="value">データを読み取るバッファ</param>
public void Read(ref Byte value)
{
value = br.ReadByte();
}
/// <summary>
/// 現在のストリームから2バイト符号なし整数を読み取り、ストリームの現在位置を2バイトだけ進めます。
/// </summary>
/// <param name="value">データを読み取るバッファ</param>
public void Read(ref UInt16 value)
{
value = br.ReadUInt16();
}
/// <summary>
/// 現在のストリームから4バイト符号なし整数を読み取り、ストリームの現在位置を4バイトだけ進めます。
/// </summary>
/// <param name="value">データを読み取るバッファ</param>
public void Read(ref UInt32 value)
{
value = br.ReadUInt32();
}
/// <summary>
/// 現在のストリームから8バイト符号なし整数を読み取り、ストリームの現在位置を8バイトだけ進めます。
/// </summary>
/// <param name="value">データを読み取るバッファ</param>
public void Read(ref UInt64 value)
{
value = br.ReadUInt64();
}
/// <summary>
/// 現在のストリームから2バイト符号付き整数を読み取り、ストリームの現在位置を2バイトだけ進めます。
/// </summary>
/// <param name="value">データを読み取るバッファ</param>
public void Read(ref Int16 value)
{
value = br.ReadInt16();
}
/// <summary>
/// 現在のストリームから4バイト符号付き整数を読み取り、ストリームの現在位置を4バイトだけ進めます。
/// </summary>
/// <param name="value">データを読み取るバッファ</param>
public void Read(ref Int32 value)
{
value = br.ReadInt32();
}
/// <summary>
/// 現在のストリームから8バイト符号付き整数を読み取り、ストリームの現在位置を8バイトだけ進めます。
/// </summary>
/// <param name="value">データを読み取るバッファ</param>
public void Read(ref Int64 value)
{
value = br.ReadInt64();
}
/// <summary>
/// 現在のストリームから指定した配列にバイトを読み取り、ストリームの現在位置を指定した配列のバイト数だけ進めます。
/// </summary>
/// <param name="value">データを読み取るバッファ</param>
public void Read(Byte[] buffer)
{
br.Read(buffer, 0, buffer.Length);
}
/// <summary>
/// 現在のストリームから指定した配列に4バイト符号なし整数を読み取り、ストリームの現在位置を指定した配列のバイト数だけ進めます。
/// </summary>
/// <param name="value">データを読み取るバッファ</param>
public void Read(UInt32[] buffer)
{
for (int i = 0; i < buffer.Length; i++)
{
Read(ref buffer[i]);
}
}
}
}

View File

@ -0,0 +1,260 @@
using System;
using System.IO;
using System.Diagnostics;
namespace TwlBackupBlock
{
/// <summary>
/// ヘッダブロックのデータ本体。
/// </summary>
public class HeaderBody : AbstractBody
{
public const int HEADER_SIZE = 160;
public const int UNIQUE_ID_SIZE = 32;
public const int HARDWARE_ID_SIZE = 16;
public const int NUM_OF_SECTION = 4;
public const int TMD_RESERVED_SIZE = 62;
public const int RESERVED_SIZE = 4;
public UInt32 signature;
public UInt16 companyCode;
public UInt16 version;
public readonly Byte[] uniqueId;
public readonly Byte[] hardwareId;
public UInt64 titleId;
public Int64 requiredSize;
public readonly UInt32[] fileSizes;
public UInt32 contentId;
public readonly TmdReserved tmdReserved;
public UInt16 contentIndex;
public readonly Byte[] reserved;
public HeaderBody(byte[] data)
{
if (data == null)
{
throw new ArgumentNullException("data");
}
if (data.Length != HEADER_SIZE)
{
string message = string.Format("data.Length % {0} != 0 (data.Length:{1})", HEADER_SIZE, data.Length);
throw new ArgumentException("data", message);
}
uniqueId = new Byte[UNIQUE_ID_SIZE];
hardwareId = new Byte[HARDWARE_ID_SIZE];
fileSizes = new UInt32[NUM_OF_SECTION];
tmdReserved = new TmdReserved();
reserved = new Byte[RESERVED_SIZE];
SetBytes(data);
}
public override byte this[int index]
{
get
{
if (index < 0 || index >= HEADER_SIZE)
{
throw new ArgumentException("index");
}
return ReadByte(index);
}
set
{
if (index < 0 || index >= HEADER_SIZE)
{
throw new ArgumentException("index");
}
WriteByte(index, value);
}
}
public override int Length
{
get
{
return HEADER_SIZE;
}
}
/// <summary>
/// 指定したバイトを取得します。
/// </summary>
/// <param name="index">取得したいバイトのインデックス。</param>
/// <returns><paramref name="index"/>で指定したバイト</returns>
public byte ReadByte(int index)
{
if (index < 0 || index >= HEADER_SIZE)
{
throw new ArgumentException("index");
}
// TODO 処理が遅ければ効率化
byte[] bytes = GetBytes();
return bytes[index];
}
/// <summary>
/// 指定したバイトを書き換えます。
/// </summary>
/// <param name="index">書き換えたいバイトのインデックス。</param>
/// <param name="b">上書きに使うバイト。</param>
public void WriteByte(int index, byte b)
{
if (index < 0 || index >= HEADER_SIZE)
{
throw new ArgumentException("index");
}
// TODO 処理が遅ければ効率化
byte[] bytes = GetBytes();
bytes[index] = b;
SetBytes(bytes);
}
public override byte[] GetBytes()
{
byte[] bytes = new byte[HEADER_SIZE];
using (var ms = new MemoryStream(bytes))
using (var bw = new BinaryWriter(ms))
{
bw.Write(signature);
bw.Write(companyCode);
bw.Write(version);
bw.Write(uniqueId);
bw.Write(hardwareId);
bw.Write(titleId);
bw.Write(requiredSize);
foreach (UInt32 e in fileSizes)
{
bw.Write(e);
}
bw.Write(contentId);
bw.Write(tmdReserved.GetBytes());
bw.Write(contentIndex);
bw.Write(reserved);
Debug.Assert(ms.Position == HEADER_SIZE);
}
return bytes;
}
public override void SetBytes(byte[] bytes)
{
if (bytes == null)
{
throw new ArgumentNullException("bytes");
}
if (bytes.Length != HEADER_SIZE)
{
string message = string.Format("bytes.Length != {0} (bytes.Length:{1})", HEADER_SIZE, bytes.Length);
throw new ArgumentException("bytes", message);
}
using (var ms = new MemoryStream(bytes))
using (var br = new ExtBinaryReader(ms))
{
br.Read(ref signature);
br.Read(ref companyCode);
br.Read(ref version);
br.Read(uniqueId);
br.Read(hardwareId);
br.Read(ref titleId);
br.Read(ref requiredSize);
br.Read(fileSizes);
br.Read(ref contentId);
{
byte[] tmdReservedBytes = new byte[TMD_RESERVED_SIZE];
br.Read(tmdReservedBytes);
tmdReserved.SetBytes(tmdReservedBytes);
}
br.Read(ref contentIndex);
br.Read(reserved);
Debug.Assert(ms.Position == HEADER_SIZE);
}
}
public override object Clone()
{
return new HeaderBody(GetBytes());
}
}
/// <summary>
/// ヘッダブロック中のTMDReserved。
/// </summary>
public class TmdReserved
{
private const int TMD_RESERVED_SIZE = 62;
public const int RESERVED8_SIZE = 3;
public const int PARENTAL_CONTROL_SIZE = 16;
public const int RESERVED_SIZE = 30;
public UInt32 publicSaveSize;
public UInt32 privateSaveSize;
public UInt32 reserved32;
public Byte flags;
public readonly Byte[] reserved8;
public readonly Byte[] parentalControl;
public readonly Byte[] reserved;
public TmdReserved()
{
reserved8 = new Byte[RESERVED8_SIZE];
parentalControl = new Byte[PARENTAL_CONTROL_SIZE];
reserved = new Byte[RESERVED_SIZE];
}
public byte[] GetBytes()
{
byte[] bytes = new byte[TMD_RESERVED_SIZE];
using (var ms = new MemoryStream(bytes))
using (var bw = new BinaryWriter(ms))
{
bw.Write(publicSaveSize);
bw.Write(privateSaveSize);
bw.Write(reserved32);
bw.Write(flags);
bw.Write(reserved8);
bw.Write(parentalControl);
bw.Write(reserved);
Debug.Assert(ms.Position == TMD_RESERVED_SIZE);
}
return bytes;
}
public void SetBytes(byte[] bytes)
{
if (bytes == null)
{
throw new ArgumentNullException("bytes");
}
if (bytes.Length != TMD_RESERVED_SIZE)
{
string message = string.Format("bytes.Length != {0} (bytes.Length:{1})", TMD_RESERVED_SIZE, bytes.Length);
throw new ArgumentException("bytes", message);
}
using (var ms = new MemoryStream(bytes))
using (var br = new ExtBinaryReader(ms))
{
br.Read(ref publicSaveSize);
br.Read(ref privateSaveSize);
br.Read(ref reserved32);
br.Read(ref flags);
br.Read(reserved8);
br.Read(parentalControl);
br.Read(reserved);
Debug.Assert(ms.Position == TMD_RESERVED_SIZE);
}
}
}
}

View File

@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// アセンブリに関する一般情報は以下の属性セットをとおして制御されます。
// アセンブリに関連付けられている情報を変更するには、
// これらの属性値を変更してください。
[assembly: AssemblyTitle("TWLBackupBlock")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Microsoft")]
[assembly: AssemblyProduct("TWLBackupBlock")]
[assembly: AssemblyCopyright("Copyright © Microsoft 2011")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// ComVisible を false に設定すると、その型はこのアセンブリ内で COM コンポーネントから
// 参照不可能になります。COM からこのアセンブリ内の型にアクセスする場合は、
// その型の ComVisible 属性を true に設定してください。
[assembly: ComVisible(false)]
// 次の GUID は、このプロジェクトが COM に公開される場合の、typelib の ID です
[assembly: Guid("46f10db4-5827-4ead-9f8c-ef885eb49f40")]
// アセンブリのバージョン情報は、以下の 4 つの値で構成されています:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// すべての値を指定するか、下のように '*' を使ってビルドおよびリビジョン番号を
// 既定値にすることができます:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@ -0,0 +1,63 @@
//------------------------------------------------------------------------------
// <auto-generated>
// このコードはツールによって生成されました。
// ランタイム バージョン:2.0.50727.4959
//
// このファイルへの変更は、以下の状況下で不正な動作の原因になったり、
// コードが再生成されるときに損失したりします。
// </auto-generated>
//------------------------------------------------------------------------------
namespace TwlBackupBlock.Properties {
using System;
/// <summary>
/// ローカライズされた文字列などを検索するための、厳密に型指定されたリソース クラスです。
/// </summary>
// このクラスは StronglyTypedResourceBuilder クラスが ResGen
// または Visual Studio のようなツールを使用して自動生成されました。
// メンバを追加または削除するには、.ResX ファイルを編集して、/str オプションと共に
// ResGen を実行し直すか、または VS プロジェクトをビルドし直します。
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "2.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// このクラスで使用されているキャッシュされた ResourceManager インスタンスを返します。
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("TwlBackupBlock.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// 厳密に型指定されたこのリソース クラスを使用して、すべての検索リソースに対し、
/// 現在のスレッドの CurrentUICulture プロパティをオーバーライドします。
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
}
}

View File

@ -0,0 +1,101 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 1.3
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">1.3</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1">this is my long string</data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
[base64 mime encoded serialized .NET Framework object]
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
[base64 mime encoded string representing a byte array form of the .NET Framework object]
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@ -0,0 +1,26 @@
//------------------------------------------------------------------------------
// <auto-generated>
// このコードはツールによって生成されました。
// ランタイム バージョン:2.0.50727.4959
//
// このファイルへの変更は、以下の状況下で不正な動作の原因になったり、
// コードが再生成されるときに損失したりします。
// </auto-generated>
//------------------------------------------------------------------------------
namespace TwlBackupBlock.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "9.0.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default {
get {
return defaultInstance;
}
}
}
}

View File

@ -0,0 +1,6 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
<Profiles>
<Profile Name="(Default)" />
</Profiles>
</SettingsFile>

View File

@ -0,0 +1,192 @@
using System;
using System.IO;
using System.Diagnostics;
namespace TwlBackupBlock
{
/// <summary>
/// 署名ブロック中のハッシュセット。
/// </summary>
public class TwlBackupHashSet
{
public const int HASH_SIZE = 32;
public const int NUM_OF_SECTION = 4;
public readonly byte[] banner;
public readonly byte[] header;
public readonly byte[][] section;
public TwlBackupHashSet()
{
banner = new byte[HASH_SIZE];
header = new byte[HASH_SIZE];
section = new byte[NUM_OF_SECTION][];
for (int i = 0; i < section.Length; i++)
{
section[i] = new byte[HASH_SIZE];
}
}
}
/// <summary>
/// 署名ブロックのデータ本体。
/// </summary>
public class SignatureBody : AbstractBody
{
public const int SIGNATURE_SIZE = 1024;
public const int ECC_SIGN_SIZE = 60;
public const int ECC_CERT_SIZE = 384;
public readonly TwlBackupHashSet digest;
public readonly byte[] sign;
public readonly byte[][] cert;
public UInt32 reserved;
public SignatureBody(byte[] data)
{
if (data == null)
{
throw new ArgumentNullException("data");
}
if (data.Length != SIGNATURE_SIZE)
{
string message = string.Format("data.Length % {0} != 0 (data.Length:{1})", SIGNATURE_SIZE, data.Length);
throw new ArgumentException("data", message);
}
digest = new TwlBackupHashSet();
sign = new byte[ECC_SIGN_SIZE];
cert = new byte[2][];
for (int i = 0; i < cert.Length; i++)
{
cert[i] = new byte[ECC_CERT_SIZE];
}
SetBytes(data);
}
public override byte this[int index]
{
get
{
if (index < 0 || index >= SIGNATURE_SIZE)
{
throw new ArgumentException("index");
}
return ReadByte(index);
}
set
{
if (index < 0 || index >= SIGNATURE_SIZE)
{
throw new ArgumentException("index");
}
WriteByte(index, value);
}
}
public override int Length
{
get
{
return SIGNATURE_SIZE;
}
}
/// <summary>
/// 指定したバイトを取得します。
/// </summary>
/// <param name="index">取得したいバイトのインデックス。</param>
/// <returns><paramref name="index"/>で指定したバイト</returns>
public byte ReadByte(int index)
{
if (index < 0 || index >= SIGNATURE_SIZE)
{
throw new ArgumentException("index");
}
// TODO 処理が遅ければ効率化
byte[] bytes = GetBytes();
return bytes[index];
}
/// <summary>
/// 指定したバイトを書き換えます。
/// </summary>
/// <param name="index">書き換えたいバイトのインデックス。</param>
/// <param name="b">上書きに使うバイト。</param>
public void WriteByte(int index, byte b)
{
if (index < 0 || index >= SIGNATURE_SIZE)
{
throw new ArgumentException("index");
}
// TODO 処理が遅ければ効率化
byte[] bytes = GetBytes();
bytes[index] = b;
SetBytes(bytes);
}
public override byte[] GetBytes()
{
byte[] bytes = new byte[SIGNATURE_SIZE];
using (var ms = new MemoryStream(bytes))
using (var bw = new BinaryWriter(ms))
{
bw.Write(digest.banner);
bw.Write(digest.header);
foreach (var e in digest.section)
{
bw.Write(e);
}
bw.Write(sign);
foreach (var e in cert)
{
bw.Write(e);
}
bw.Write(reserved);
Debug.Assert(ms.Position == SIGNATURE_SIZE);
}
return bytes;
}
public override void SetBytes(byte[] bytes)
{
if (bytes == null)
{
throw new ArgumentNullException("bytes");
}
if (bytes.Length != SIGNATURE_SIZE)
{
string message = string.Format("bytes.Length != {0} (bytes.Length:{1})", SIGNATURE_SIZE, bytes.Length);
throw new ArgumentException("bytes", message);
}
using (var ms = new MemoryStream(bytes))
using (var br = new ExtBinaryReader(ms))
{
br.Read(digest.banner);
br.Read(digest.header);
foreach (var e in digest.section)
{
br.Read(e);
}
br.Read(sign);
foreach (var e in cert)
{
br.Read(e);
}
br.Read(ref reserved);
Debug.Assert(ms.Position == SIGNATURE_SIZE);
}
}
public override object Clone()
{
return new SignatureBody(GetBytes());
}
}
}

View File

@ -0,0 +1 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" />

View File

@ -0,0 +1,93 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>9.0.30729</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{E62F6169-868A-48E4-9B1D-C2EB3BE11F39}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>TwlBackupBlock</RootNamespace>
<AssemblyName>TwlBackupBlock</AssemblyName>
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DocumentationFile>document.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DocumentationFile>document.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
<Reference Include="System.Xml.Linq">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
<Reference Include="System.Data.DataSetExtensions">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="AesCmac.cs" />
<Compile Include="Body.cs" />
<Compile Include="ExtBinaryReader.cs" />
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
<DependentUpon>Settings.settings</DependentUpon>
</Compile>
<Compile Include="Utility.cs" />
<Compile Include="AbstractBody.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="HeaderBody.cs" />
<Compile Include="SignatureBody.cs" />
<Compile Include="Block.cs" />
<Compile Include="Blocks.cs" />
</ItemGroup>
<ItemGroup>
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@ -0,0 +1,496 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
namespace TwlBackupBlock
{
/// <summary>
/// TWLバックアップデータの暗号化・復号化処理を提供します。
/// </summary>
public class Utility
{
private const int KEY_SIZE = 16;
private const int ROUND_SCALE = 16;
private const int AES_MAC_SIZE = 16;
private const int AES_IV_SIZE = 16;
private const int AES_SIGN_HEADER_SIZE = 32;
private const int BANNER_SIZE = 16 * 1024;
private const int HEADER_SIZE = 160;
private const int SIGNATURE_SIZE = 1024;
private const int ENCRYPT_BANNER_SIZE = BANNER_SIZE + AES_SIGN_HEADER_SIZE;
private const int ENCRYPT_HEADER_SIZE = HEADER_SIZE + AES_SIGN_HEADER_SIZE;
private const int ENCRYPT_SIGNATURE_SIZE = SIGNATURE_SIZE + AES_SIGN_HEADER_SIZE;
/// <summary>
/// バックアップデータをブロックに分割して復号化をおこないます。
/// </summary>
/// <param name="data">バックアップデータ。</param>
/// <param name="key">データ本体の復号鍵。</param>
/// <returns>分割、復号化されたブロック。</returns>
public static Blocks DecryptBackupData(byte[] data, byte[] key)
{
if (data == null)
{
throw new ArgumentNullException("data");
}
if (key == null)
{
throw new ArgumentNullException("key");
}
{
int minDataSize = ENCRYPT_BANNER_SIZE + ENCRYPT_HEADER_SIZE + ENCRYPT_SIGNATURE_SIZE;
if (data.Length < minDataSize)
{
string message = string.Format("data.Length < {0} (data.Length:{1})", data, minDataSize);
throw new ArgumentException("data", message);
}
}
if (key.Length != KEY_SIZE)
{
string message = string.Format("key.Length != {0} (key.Length:{1})", KEY_SIZE, key.Length);
throw new ArgumentException("key", message);
}
Blocks blocks = new Blocks();
using (var ms = new MemoryStream(data))
using (var br = new BinaryReader(ms))
{
blocks.banner.body = new Body(DecryptBlock(br, BANNER_SIZE, key, out blocks.banner.mac, out blocks.banner.iv));
HeaderBody headerBody = new HeaderBody(DecryptBlock(br, HEADER_SIZE, key, out blocks.header.mac, out blocks.header.iv));
blocks.header.body = headerBody;
if (!CheckSignature(headerBody))
{
throw new Exception("ヘッダブロックのシグネチャが正しくありません");
}
blocks.signature.body = new SignatureBody(DecryptBlock(br, SIGNATURE_SIZE, key, out blocks.signature.mac, out blocks.signature.iv));
int TMD_SIZE = (int)headerBody.fileSizes[0];
int CONTENT_SIZE = (int)headerBody.fileSizes[1];
int SAVE_DATA_SIZE = (int)headerBody.fileSizes[2];
int SUB_BANNER_SIZE = (int)headerBody.fileSizes[3];
blocks.tmd.body = new Body(DecryptBlock(br, TMD_SIZE, key, out blocks.tmd.mac, out blocks.tmd.iv));
blocks.content.body = new Body(DecryptBlock(br, CONTENT_SIZE, key, out blocks.content.mac, out blocks.content.iv));
blocks.saveData.body = new Body(DecryptBlock(br, SAVE_DATA_SIZE, key, out blocks.saveData.mac, out blocks.saveData.iv));
blocks.subBanner.body = new Body(DecryptBlock(br, SUB_BANNER_SIZE, key, out blocks.subBanner.mac, out blocks.subBanner.iv));
Debug.Assert(ms.Position == data.Length);
}
return blocks;
}
/// <summary>
/// ヘッダのデータ本体のシグネチャが正しいか判定します。
/// </summary>
/// <param name="header">ヘッダのデータ本体。</param>
/// <returns>シグネチャが正しい場合はtrue。それ以外の場合はfalse。</returns>
private static bool CheckSignature(HeaderBody header)
{
byte[] signature = { (byte)'3', (byte)'F', (byte)'D', (byte)'T' };
byte[] bytes = BitConverter.GetBytes(header.signature);
Debug.Assert(signature.Length == bytes.Length);
for (int i = 0; i < bytes.Length; i++)
{
if (bytes[i] != signature[i])
{
return false;
}
}
return true;
}
/// <summary>
/// binaryReaderから読み込んだブロックを復号化します。
/// </summary>
/// <param name="binaryReader">ブロックを読み出すBinaryReader。</param>
/// <param name="bodySize">暗号化前のブロックのデータ本体のバイトサイズ。</param>
/// <param name="key">データ本体の復号鍵。</param>
/// <param name="mac">ブロックのMAC。</param>
/// <param name="iv">ブロックのIV。</param>
/// <returns>ブロックのデータ本体。</returns>
private static byte[] DecryptBlock(BinaryReader binaryReader, int bodySize, byte[] key, out byte[] mac, out byte[] iv)
{
Debug.Assert(key.Length == KEY_SIZE);
int encryptBodySize = RoundUp(bodySize, ROUND_SCALE);
byte[] body = binaryReader.ReadBytes(encryptBodySize);
mac = binaryReader.ReadBytes(AES_MAC_SIZE);
iv = binaryReader.ReadBytes(AES_IV_SIZE);
return DecryptBody(body, key, iv, bodySize);
}
/// <summary>
/// ブロックのデータ本体を復号化します。
/// </summary>
/// <param name="body">暗号化されたデータ本体。</param>
/// <param name="key">データ本体の復号鍵。</param>
/// <param name="iv">暗号化に利用したIV。</param>
/// <param name="size">暗号化前のブロックのデータ本体のバイトサイズ。</param>
/// <returns>復号化されたデータ本体。</returns>
public static byte[] DecryptBody(byte[] body, byte[] key, byte[] iv, int size)
{
if (body == null)
{
throw new ArgumentNullException("body");
}
if (key == null)
{
throw new ArgumentNullException("key");
}
if (iv == null)
{
throw new ArgumentNullException("iv");
}
if (body.Length % ROUND_SCALE != 0)
{
string message = string.Format("body.Length % {0} != 0 (body.Length:{1})", ROUND_SCALE, body.Length);
throw new ArgumentException("body", message);
}
if (key.Length != KEY_SIZE)
{
string message = string.Format("key.Length != {0} (key.Length:{1})", KEY_SIZE, key.Length);
throw new ArgumentException("key", message);
}
if (iv.Length != AES_IV_SIZE)
{
string message = string.Format("iv.Length != {0} (iv.Length:{1})", AES_IV_SIZE, iv.Length);
throw new ArgumentException("iv", message);
}
if (size < 0)
{
string message = string.Format("size < 0 (size:{0})", size);
throw new ArgumentException("size", message);
}
if (size > body.Length)
{
string message = string.Format("size > body.Length (size:{0}, body.Length:{1})", size, body.Length);
throw new ArgumentException("size", message);
}
byte[] result = new byte[size];
using (var ms = new MemoryStream(body))
{
RijndaelManaged aes = new RijndaelManaged();
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.Zeros;
aes.Key = key;
aes.IV = iv;
using (CryptoStream cs = new CryptoStream(ms, aes.CreateDecryptor(), CryptoStreamMode.Read))
{
cs.Read(result, 0, result.Length);
}
}
return result;
}
/// <summary>
/// ブロックのデータ本体を暗号化します。
/// </summary>
/// <param name="body">復号化されたデータ本体。</param>
/// <param name="key">データ本体の暗号鍵。</param>
/// <param name="iv">暗号化に利用するIV。</param>
/// <returns>暗号化されたデータ本体。</returns>
public static byte[] EncryptBody(byte[] body, byte[] key, byte[] iv)
{
if (body == null)
{
throw new ArgumentNullException("body");
}
if (key == null)
{
throw new ArgumentNullException("key");
}
if (iv == null)
{
throw new ArgumentNullException("iv");
}
if (key.Length != KEY_SIZE)
{
string message = string.Format("key.Length != {0} (key.Length:{1})", KEY_SIZE, key.Length);
throw new ArgumentException("key", message);
}
if (iv.Length != AES_IV_SIZE)
{
string message = string.Format("iv.Length != {0} (iv.Length:{1})", AES_IV_SIZE, iv.Length);
throw new ArgumentException("iv", message);
}
byte[] result = new byte[RoundUp(body.Length, ROUND_SCALE)];
using (var ms = new MemoryStream(body))
{
RijndaelManaged aes = new RijndaelManaged();
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.Zeros;
aes.Key = key;
aes.IV = iv;
using (CryptoStream cs = new CryptoStream(ms, aes.CreateEncryptor(), CryptoStreamMode.Read))
{
cs.Read(result, 0, result.Length);
}
}
return result;
}
/// <summary>
/// ブロックのデータ本体を暗号化します。内部的にはEncryptBody(body.GetBytes(), key, iv)を呼び出します。
/// </summary>
/// <param name="body">暗号化されたデータ本体。</param>
/// <param name="key">データ本体の暗号鍵。</param>
/// <param name="iv">暗号化に利用するIV。</param>
/// <returns>暗号化されたデータ本体。</returns>
public static byte[] EncryptBody(AbstractBody body, byte[] key, byte[] iv)
{
if (body == null)
{
throw new ArgumentNullException("body");
}
return EncryptBody(body.GetBytes(), key, iv);
}
/// <summary>
/// ブロックのデータ本体からハッシュを取得します。
/// </summary>
/// <param name="data">暗号化前のデータ本体。</param>
/// <returns>32バイトのハッシュ。</returns>
public static byte[] GetSha256(byte[] data)
{
if (data == null)
{
throw new ArgumentNullException("data");
}
var sha = new SHA256Managed();
byte[] buf = Enumerable.Repeat<byte>(0, RoundUp(data.Length, ROUND_SCALE)).ToArray();
Array.Copy(data, 0, buf, 0, data.Length);
return sha.ComputeHash(buf);
}
/// <summary>
/// ハッシュからMACを取得します。
/// </summary>
/// <param name="data">データ本体のハッシュ。</param>
/// <param name="key">AES-CMACの鍵。</param>
/// <returns>16バイトのMAC。</returns>
public static byte[] GetAesCmac(byte[] data, byte[] key)
{
return AesCmac.GetMac(data, key);
}
/// <summary>
/// 基準値の倍数のうち、指定された整数以上で最小のものを返します。
/// </summary>
/// <param name="x">整数。</param>
/// <param name="y">基準値。</param>
/// <returns>yの倍数のうち、x以上で最小の整数。</returns>
private static int RoundUp(int x, int y)
{
return (x + y - 1) / y * y;
}
/// <summary>
/// インポートシーケンスと同様の手順でバックアップデータを検証します。
/// ただし、このメソッドでは署名データの検証はおこないません。
/// 署名以外の整合性がとれており、署名の整合性がとれていない場合、このメソッドはtrueを返します。
/// </summary>
/// <param name="data">検証にかけるバックアップデータ。</param>
/// <param name="aesCbcKey">データ本体の暗号鍵。</param>
/// <param name="aesCmacKey">MACの鍵。</param>
/// <returns>検証に成功した場合はtrue。それ以外の場合はfalse。</returns>
public static bool Verify(byte[] data, byte[] aesCbcKey, byte[] aesCmacKey)
{
if (data == null)
{
throw new ArgumentNullException("data");
}
if (aesCbcKey == null)
{
throw new ArgumentNullException("key");
}
if (aesCbcKey.Length != KEY_SIZE)
{
string message = string.Format("key.Length != {0} (key.Length:{1})", KEY_SIZE, aesCbcKey.Length);
throw new ArgumentException("key", message);
}
{
int minDataSize = ENCRYPT_BANNER_SIZE + ENCRYPT_HEADER_SIZE + ENCRYPT_SIGNATURE_SIZE;
if (data.Length < minDataSize)
{
Console.WriteLine("バックアップファイルサイズがバナー+ヘッダ+署名ブロック未満です");
return false;
}
}
Blocks blocks = new Blocks();
using (var ms = new MemoryStream(data))
using (var br = new BinaryReader(ms))
{
blocks.banner.body = new Body(DecryptBlock(br, BANNER_SIZE, aesCbcKey, out blocks.banner.mac, out blocks.banner.iv));
HeaderBody headerBody = new HeaderBody(DecryptBlock(br, HEADER_SIZE, aesCbcKey, out blocks.header.mac, out blocks.header.iv));
blocks.header.body = headerBody;
SignatureBody signatureBody = new SignatureBody(DecryptBlock(br, SIGNATURE_SIZE, aesCbcKey, out blocks.signature.mac, out blocks.signature.iv));
blocks.signature.body = signatureBody;
if (!CheckMac(blocks.header, aesCmacKey, blocks.header.mac))
{
Console.WriteLine("ヘッダブロックのMACが一致しません");
return false;
}
if (!CheckSignature(headerBody))
{
Console.WriteLine("ヘッダブロックのシグネチャが正しくありません");
return false;
}
if (!CheckMac(blocks.signature, aesCmacKey, blocks.signature.mac))
{
Console.WriteLine("署名ブロックのMACが一致しません");
return false;
}
/* 署名データの検証 */
if (!CheckHash(blocks.header, signatureBody.digest.header))
{
Console.WriteLine("ヘッダブロックのハッシュが一致しません");
return false;
}
if (!CheckMac(blocks.banner, aesCmacKey, blocks.banner.mac))
{
Console.WriteLine("バナーブロックのMACが一致しません");
return false;
}
if (!CheckHash(blocks.banner, signatureBody.digest.banner))
{
Console.WriteLine("バナーブロックのハッシュが一致しません");
return false;
}
int TMD_SIZE = (int)headerBody.fileSizes[0];
int CONTENT_SIZE = (int)headerBody.fileSizes[1];
int SAVE_DATA_SIZE = (int)headerBody.fileSizes[2];
int SUB_BANNER_SIZE = (int)headerBody.fileSizes[3];
int restSize = RoundUp(TMD_SIZE, ROUND_SCALE) + RoundUp(CONTENT_SIZE, ROUND_SCALE) +
RoundUp(SAVE_DATA_SIZE, ROUND_SCALE) + RoundUp(SUB_BANNER_SIZE, ROUND_SCALE) +
AES_SIGN_HEADER_SIZE * 4;
if (data.Length - ms.Position != restSize)
{
Console.WriteLine("TMD、コンテンツ、publicセーブデータ、サブバナーブロックサイズが実際のサイズと一致しません");
return false;
}
blocks.tmd.body = new Body(DecryptBlock(br, TMD_SIZE, aesCbcKey, out blocks.tmd.mac, out blocks.tmd.iv));
if (!CheckMac(blocks.tmd, aesCmacKey, blocks.tmd.mac))
{
Console.WriteLine("TMDブロックのMACが一致しません");
return false;
}
if (!CheckHash(blocks.tmd, signatureBody.digest.section[0]))
{
Console.WriteLine("TMDブロックのハッシュが一致しません");
return false;
}
blocks.content.body = new Body(DecryptBlock(br, CONTENT_SIZE, aesCbcKey, out blocks.content.mac, out blocks.content.iv));
if (!CheckMac(blocks.content, aesCmacKey, blocks.content.mac))
{
Console.WriteLine("コンテンツブロックのMACが一致しません");
return false;
}
if (!CheckHash(blocks.content, signatureBody.digest.section[1]))
{
Console.WriteLine("コンテンツブロックのハッシュが一致しません");
return false;
}
blocks.saveData.body = new Body(DecryptBlock(br, SAVE_DATA_SIZE, aesCbcKey, out blocks.saveData.mac, out blocks.saveData.iv));
if (!CheckMac(blocks.saveData, aesCmacKey, blocks.saveData.mac))
{
Console.WriteLine("publicセーブデータブロックのMACが一致しません");
return false;
}
if (!CheckHash(blocks.saveData, signatureBody.digest.section[2]))
{
Console.WriteLine("publicセーブデータブロックのハッシュが一致しません");
return false;
}
blocks.subBanner.body = new Body(DecryptBlock(br, SUB_BANNER_SIZE, aesCbcKey, out blocks.subBanner.mac, out blocks.subBanner.iv));
if (!CheckMac(blocks.subBanner, aesCmacKey, blocks.subBanner.mac))
{
Console.WriteLine("サブバナーブロックの内容とMACが一致しません");
return false;
}
if (!CheckHash(blocks.subBanner, signatureBody.digest.section[3]))
{
Console.WriteLine("サブバナーブロックのハッシュが一致しません");
return false;
}
Debug.Assert(ms.Position == data.Length);
}
return true;
}
/// <summary>
/// ハッシュによりブロックが正しいか判定します。
/// </summary>
/// <param name="block">ブロック。</param>
/// <param name="hash">ハッシュ。</param>
/// <returns>ハッシュがブロックから生成したものと一致した場合はtrue。それ以外の場合はfalse。</returns>
private static bool CheckHash(Block block, byte[] hash)
{
byte[] tryHash = GetSha256(block.body.GetBytes());
Debug.Assert(tryHash.Length == hash.Length);
for (int i = 0; i < tryHash.Length; i++)
{
if (tryHash[i] != hash[i])
{
return false;
}
}
return true;
}
/// <summary>
/// MACによりブロックが正しいか判定します。
/// </summary>
/// <param name="block">ブロック。</param>
/// <param name="key">MACの鍵。</param>
/// <param name="mac">MAC。</param>
/// <returns>MACがブロックから生成したものと一致した場合はtrue。それ以外の場合はfalse。</returns>
private static bool CheckMac(Block block, byte[] key, byte[] mac)
{
byte[] tryMac = GetAesCmac(GetSha256(block.body.GetBytes()), key);
Debug.Assert(tryMac.Length == mac.Length);
for (int i = 0; i < tryMac.Length; ++i)
{
if (tryMac[i] != mac[i])
{
return false;
}
}
return true;
}
}
}