NAX0 key derivation

This commit is contained in:
Alex Barney 2018-06-20 12:42:56 -05:00
parent d8e41bb1d9
commit 07a42ed731
18 changed files with 2267 additions and 4 deletions

3
.gitignore vendored
View file

@ -258,4 +258,5 @@ paket-files/
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
*.pyc
**/launchSettings.json

351
libhac/CombinationStream.cs Normal file
View file

@ -0,0 +1,351 @@
//-----------------------------------------------------------------------
// <copyright file="CombinationStream.cs" company="The Outercurve Foundation">
// Copyright (c) 2011, The Outercurve Foundation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
// <author>Prabir Shrestha (prabir.me)</author>
// <website>https://github.com/facebook-csharp-sdk/combination-stream</website>
//-----------------------------------------------------------------------
/*
* Install-Package CombinationStream
*
*/
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
namespace libhac
{
internal class CombinationStream : Stream
{
private readonly IList<Stream> _streams;
private readonly IList<int> _streamsToDispose;
private readonly IList<long> _streamsStartPos;
private int _currentStreamIndex;
private Stream _currentStream;
private long _length = -1;
private long _postion;
public CombinationStream(IList<Stream> streams)
: this(streams, null)
{
}
public CombinationStream(IList<Stream> streams, IList<int> streamsToDispose)
{
if (streams == null)
throw new ArgumentNullException("streams");
_streams = streams;
_streamsToDispose = streamsToDispose;
if (streams.Count > 0)
_currentStream = streams[_currentStreamIndex++];
_streamsStartPos = new List<long>(streams.Count);
long pos = 0;
foreach (var strm in streams)
{
_streamsStartPos.Add(pos);
pos += strm.Length;
}
}
public IList<Stream> InternalStreams => _streams;
public override void Flush() => _currentStream?.Flush();
public override long Seek(long offset, SeekOrigin origin)
{
long pos = 0;
switch (origin)
{
case SeekOrigin.Begin:
pos = offset;
break;
case SeekOrigin.Current:
pos = Position + offset;
break;
case SeekOrigin.End:
pos = Length + offset;
break;
}
int idx = 0;
while (idx+1 < _streamsStartPos.Count)
{
if (_streamsStartPos[idx + 1] > pos)
{
break;
}
idx++;
}
_currentStreamIndex = idx;
_currentStream = _streams[_currentStreamIndex];
_currentStream.Seek(pos - _streamsStartPos[idx], SeekOrigin.Begin);
_postion = pos;
return _postion;
//throw new InvalidOperationException("Stream is not seekable.");
}
public override void SetLength(long value)
{
_length = value;
}
public override int Read(byte[] buffer, int offset, int count)
{
int result = 0;
int buffPostion = offset;
while (count > 0)
{
int bytesRead = _currentStream.Read(buffer, buffPostion, count);
result += bytesRead;
buffPostion += bytesRead;
_postion += bytesRead;
if (bytesRead <= count)
count -= bytesRead;
if (count > 0)
{
if (_currentStreamIndex >= _streams.Count)
break;
_currentStream = _streams[_currentStreamIndex++];
}
}
return result;
}
#if NETFX_CORE
public async override System.Threading.Tasks.Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
int result = 0;
int buffPostion = offset;
while (count > 0)
{
int bytesRead = await _currentStream.ReadAsync(buffer, buffPostion, count, cancellationToken);
result += bytesRead;
buffPostion += bytesRead;
_postion += bytesRead;
if (bytesRead <= count)
count -= bytesRead;
if (count > 0)
{
if (_currentStreamIndex >= _streams.Count)
break;
_currentStream = _streams[_currentStreamIndex++];
}
}
return result;
}
public override System.Threading.Tasks.Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
throw new InvalidOperationException("Stream is not writable");
}
#else
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
{
CombinationStreamAsyncResult asyncResult = new CombinationStreamAsyncResult(state);
if (count > 0)
{
int buffPostion = offset;
AsyncCallback rc = null;
rc = readresult =>
{
try
{
int bytesRead = _currentStream.EndRead(readresult);
asyncResult.BytesRead += bytesRead;
buffPostion += bytesRead;
_postion += bytesRead;
if (bytesRead <= count)
count -= bytesRead;
if (count > 0)
{
if (_currentStreamIndex >= _streams.Count)
{
// done
asyncResult.CompletedSynchronously = false;
asyncResult.SetAsyncWaitHandle();
asyncResult.IsCompleted = true;
callback(asyncResult);
}
else
{
_currentStream = _streams[_currentStreamIndex++];
_currentStream.BeginRead(buffer, buffPostion, count, rc, readresult.AsyncState);
}
}
else
{
// done
asyncResult.CompletedSynchronously = false;
asyncResult.SetAsyncWaitHandle();
asyncResult.IsCompleted = true;
callback(asyncResult);
}
}
catch (Exception ex)
{
// done
asyncResult.Exception = ex;
asyncResult.CompletedSynchronously = false;
asyncResult.SetAsyncWaitHandle();
asyncResult.IsCompleted = true;
callback(asyncResult);
}
};
_currentStream.BeginRead(buffer, buffPostion, count, rc, state);
}
else
{
// done
asyncResult.CompletedSynchronously = true;
asyncResult.SetAsyncWaitHandle();
asyncResult.IsCompleted = true;
callback(asyncResult);
}
return asyncResult;
}
public override int EndRead(IAsyncResult asyncResult)
{
// todo: check if it is of same reference
asyncResult.AsyncWaitHandle.WaitOne();
var ar = (CombinationStreamAsyncResult)asyncResult;
if (ar.Exception != null)
{
throw ar.Exception;
}
return ar.BytesRead;
}
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
{
throw new InvalidOperationException("Stream is not writable");
}
internal class CombinationStreamAsyncResult : IAsyncResult
{
private readonly object _asyncState;
public CombinationStreamAsyncResult(object asyncState)
{
_asyncState = asyncState;
_manualResetEvent = new ManualResetEvent(false);
}
public bool IsCompleted { get; internal set; }
public WaitHandle AsyncWaitHandle
{
get { return _manualResetEvent; }
}
public object AsyncState
{
get { return _asyncState; }
}
public bool CompletedSynchronously { get; internal set; }
public Exception Exception { get; internal set; }
internal void SetAsyncWaitHandle()
{
_manualResetEvent.Set();
}
private readonly ManualResetEvent _manualResetEvent;
public int BytesRead;
}
#endif
public override void Write(byte[] buffer, int offset, int count)
{
throw new InvalidOperationException("Stream is not writable");
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (_streamsToDispose == null)
{
foreach (var stream in InternalStreams)
stream.Dispose();
}
else
{
int i;
for (i = 0; i < InternalStreams.Count; i++)
InternalStreams[i].Dispose();
}
}
public override bool CanRead
{
get { return true; }
}
public override bool CanSeek
{
get { return true; }
}
public override bool CanWrite
{
get { return false; }
}
public override long Length
{
get
{
if (_length == -1)
{
_length = 0;
foreach (var stream in _streams)
_length += stream.Length;
}
return _length;
}
}
public override long Position
{
get => _postion;
set => Seek(value, SeekOrigin.Begin);
}
}
}

42
libhac/Crypto.cs Normal file
View file

@ -0,0 +1,42 @@
using System.IO;
using System.Security.Cryptography;
namespace libhac
{
public class Crypto
{
public static void DecryptEcb(byte[] key, byte[] src, int srcIndex, byte[] dest, int destIndex, int length)
{
using (var aes = Aes.Create())
{
if(aes == null) throw new CryptographicException("Unable to create AES object");
aes.Key = key;
aes.Mode = CipherMode.ECB;
aes.Padding = PaddingMode.None;
var dec = aes.CreateDecryptor();
using (var ms = new MemoryStream(dest, destIndex, length))
using (var cs = new CryptoStream(ms, dec, CryptoStreamMode.Write))
{
cs.Write(src, srcIndex, length);
cs.FlushFinalBlock();
}
}
}
public static void DecryptEcb(byte[] key, byte[] src, byte[] dest, int length) =>
DecryptEcb(key, src, 0, dest, 0, length);
public static void GenerateKek(byte[] dst, byte[] src, byte[] masterKey, byte[] kekSeed, byte[] keySeed)
{
var kek = new byte[0x10];
var srcKek = new byte[0x10];
DecryptEcb(masterKey, kekSeed, kek, 0x10);
DecryptEcb(kek, src, srcKek, 0x10);
if (keySeed != null)
{
DecryptEcb(srcKek, keySeed, dst, 0x10);
}
}
}
}

29
libhac/IProgressReport.cs Normal file
View file

@ -0,0 +1,29 @@
namespace libhac
{
public interface IProgressReport
{
/// <summary>
/// Sets the current value of the <see cref="IProgressReport"/> to <paramref name="value"/>.
/// </summary>
/// <param name="value">The value to set.</param>
void Report(long value);
/// <summary>
/// Adds <paramref name="value"/> to the current value of the <see cref="IProgressReport"/>.
/// </summary>
/// <param name="value">The amount to add.</param>
void ReportAdd(long value);
/// <summary>
/// Sets the maximum value of the <see cref="IProgressReport"/> to <paramref name="value"/>.
/// </summary>
/// <param name="value">The maximum value to set.</param>
void SetTotal(long value);
/// <summary>
/// Logs a message to the <see cref="IProgressReport"/> object.
/// </summary>
/// <param name="message">The message to output.</param>
void LogMessage(string message);
}
}

171
libhac/Keyset.cs Normal file
View file

@ -0,0 +1,171 @@
// ReSharper disable InconsistentNaming
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace libhac
{
public class Keyset
{
public byte[] secure_boot_key { get; set; } = new byte[0x10];
public byte[] tsec_key { get; set; } = new byte[0x10];
public byte[][] keyblob_keys { get; set; } = Util.CreateJaggedArray<byte[][]>(0x20, 0x10);
public byte[][] keyblob_mac_keys { get; set; } = Util.CreateJaggedArray<byte[][]>(0x20, 0x10);
public byte[][] encrypted_keyblobs { get; set; } = Util.CreateJaggedArray<byte[][]>(0x20, 0xB0);
public byte[][] keyblobs { get; set; } = Util.CreateJaggedArray<byte[][]>(0x20, 0x90);
public byte[][] keyblob_key_sources { get; set; } = Util.CreateJaggedArray<byte[][]>(0x20, 0x10);
public byte[] keyblob_mac_key_source { get; set; } = new byte[0x10];
public byte[] master_key_source { get; set; } = new byte[0x10];
public byte[][] master_keys { get; set; } = Util.CreateJaggedArray<byte[][]>(0x20, 0x10);
public byte[][] package1_keys { get; set; } = Util.CreateJaggedArray<byte[][]>(0x20, 0x10);
public byte[][] package2_keys { get; set; } = Util.CreateJaggedArray<byte[][]>(0x20, 0x10);
public byte[] package2_key_source { get; set; } = new byte[0x10];
public byte[] aes_kek_generation_source { get; set; } = new byte[0x10];
public byte[] aes_key_generation_source { get; set; } = new byte[0x10];
public byte[] key_area_key_application_source { get; set; } = new byte[0x10];
public byte[] key_area_key_ocean_source { get; set; } = new byte[0x10];
public byte[] key_area_key_system_source { get; set; } = new byte[0x10];
public byte[] titlekek_source { get; set; } = new byte[0x10];
public byte[] header_kek_source { get; set; } = new byte[0x10];
public byte[] sd_card_kek_source { get; set; } = new byte[0x10];
public byte[][] sd_card_key_sources { get; set; } = Util.CreateJaggedArray<byte[][]>(2, 0x20);
public byte[] encrypted_header_key { get; set; } = new byte[0x20];
public byte[] header_key { get; set; } = new byte[0x20];
public byte[][] titlekeks { get; set; } = Util.CreateJaggedArray<byte[][]>(0x20, 0x10);
public byte[][][] key_area_keys { get; set; } = Util.CreateJaggedArray<byte[][][]>(0x20, 3, 0x10);
public byte[][] sd_card_keys { get; set; } = Util.CreateJaggedArray<byte[][]>(2, 0x20);
public byte[] nca_hdr_fixed_key_modulus { get; set; } = new byte[0x100];
public byte[] acid_fixed_key_modulus { get; set; } = new byte[0x100];
public byte[] package2_fixed_key_modulus { get; set; } = new byte[0x100];
public void SetSdSeed(byte[] sdseed)
{
foreach (byte[] key in sd_card_key_sources)
{
for (int i = 0; i < 0x20; i++)
{
key[i] ^= sdseed[i & 0xF];
}
}
DeriveKeys();
}
private void DeriveKeys()
{
//var cmac = new byte[0x10];
//for (int i = 0; i < 0x20; i++)
//{
// Crypto.DecryptEcb(tsec_key, keyblob_key_sources[i], keyblob_keys[i], 0x10);
// Crypto.DecryptEcb(secure_boot_key, keyblob_keys[i], keyblob_keys[i], 0x10);
// Crypto.DecryptEcb(keyblob_keys[i], keyblob_mac_key_source, keyblob_mac_keys[i], 0x10);
//}
var sdKek = new byte[0x10];
Crypto.GenerateKek(sdKek, sd_card_kek_source, master_keys[0], aes_kek_generation_source, aes_key_generation_source);
for (int k = 0; k < sd_card_key_sources.Length; k++)
{
Crypto.DecryptEcb(sdKek, sd_card_key_sources[k], sd_card_keys[k], 0x20);
}
}
}
public static class ExternalKeys
{
private static readonly Dictionary<string, KeyValue> KeyDict = CreateKeyDict();
public static Keyset ReadKeyFile(string filename)
{
var keyset = new Keyset();
using (var reader = new StreamReader(new FileStream(filename, FileMode.Open)))
{
string line;
while ((line = reader.ReadLine()) != null)
{
var a = line.Split('=');
if (a.Length != 2) continue;
var key = a[0].Trim();
var valueStr = a[1].Trim();
if (!KeyDict.TryGetValue(key, out var kv))
{
Console.WriteLine($"Failed to match key {key}");
continue;
}
var value = valueStr.ToBytes();
if (value.Length != kv.Size)
{
Console.WriteLine($"Key {key} had incorrect size {value.Length}. (Expected {kv.Size})");
continue;
}
kv.Assign(keyset, value);
}
}
return keyset;
}
private static Dictionary<string, KeyValue> CreateKeyDict()
{
var keys = new List<KeyValue>
{
new KeyValue("aes_kek_generation_source", 0x10, (set, key) => set.aes_kek_generation_source = key),
new KeyValue("aes_key_generation_source", 0x10, (set, key) => set.aes_key_generation_source = key),
new KeyValue("key_area_key_application_source", 0x10, (set, key) => set.key_area_key_application_source = key),
new KeyValue("key_area_key_ocean_source", 0x10, (set, key) => set.key_area_key_ocean_source = key),
new KeyValue("key_area_key_system_source", 0x10, (set, key) => set.key_area_key_system_source = key),
new KeyValue("titlekek_source", 0x10, (set, key) => set.titlekek_source = key),
new KeyValue("header_kek_source", 0x10, (set, key) => set.header_kek_source = key),
new KeyValue("header_key_source", 0x20, (set, key) => set.encrypted_header_key = key),
new KeyValue("header_key", 0x20, (set, key) => set.header_key = key),
new KeyValue("encrypted_header_key", 0x20, (set, key) => set.encrypted_header_key = key),
new KeyValue("package2_key_source", 0x10, (set, key) => set.package2_key_source = key),
new KeyValue("sd_card_kek_source", 0x10, (set, key) => set.sd_card_kek_source = key),
new KeyValue("sd_card_nca_key_source", 0x20, (set, key) => set.sd_card_key_sources[1] = key),
new KeyValue("sd_card_save_key_source", 0x20, (set, key) => set.sd_card_key_sources[0] = key),
new KeyValue("master_key_source", 0x10, (set, key) => set.master_key_source = key),
new KeyValue("keyblob_mac_key_source", 0x10, (set, key) => set.keyblob_mac_key_source = key),
new KeyValue("secure_boot_key", 0x10, (set, key) => set.secure_boot_key = key),
new KeyValue("tsec_key", 0x10, (set, key) => set.tsec_key = key)
};
for (int slot = 0; slot < 0x20; slot++)
{
int i = slot;
keys.Add(new KeyValue($"keyblob_key_source_{i:D2}", 0x10, (set, key) => set.keyblob_key_sources[i] = key));
keys.Add(new KeyValue($"keyblob_key_{i:D2}", 0x10, (set, key) => set.keyblob_keys[i] = key));
keys.Add(new KeyValue($"keyblob_mac_key_{i:D2}", 0x10, (set, key) => set.keyblob_mac_keys[i] = key));
keys.Add(new KeyValue($"encrypted_keyblob_{i:D2}", 0xB0, (set, key) => set.encrypted_keyblobs[i] = key));
keys.Add(new KeyValue($"keyblob_{i:D2}", 0x90, (set, key) => set.keyblobs[i] = key));
keys.Add(new KeyValue($"master_key_{i:D2}", 0x10, (set, key) => set.master_keys[i] = key));
keys.Add(new KeyValue($"package1_key_{i:D2}", 0x10, (set, key) => set.package1_keys[i] = key));
keys.Add(new KeyValue($"package2_key_{i:D2}", 0x10, (set, key) => set.package2_keys[i] = key));
keys.Add(new KeyValue($"titlekek_{i:D2}", 0x10, (set, key) => set.titlekeks[i] = key));
keys.Add(new KeyValue($"key_area_key_application_{i:D2}", 0x10, (set, key) => set.key_area_keys[i][0] = key));
keys.Add(new KeyValue($"key_area_key_ocean_{i:D2}", 0x10, (set, key) => set.key_area_keys[i][1] = key));
keys.Add(new KeyValue($"key_area_key_system_{i:D2}", 0x10, (set, key) => set.key_area_keys[i][2] = key));
}
return keys.ToDictionary(k => k.Name, k => k);
}
private class KeyValue
{
public string Name;
public int Size;
public Action<Keyset, byte[]> Assign;
public KeyValue(string name, int size, Action<Keyset, byte[]> assign)
{
Name = name;
Size = size;
Assign = assign;
}
}
}
}

96
libhac/Nax0.cs Normal file
View file

@ -0,0 +1,96 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using libhac.XTSSharp;
namespace libhac
{
public class Nax0
{
public List<string> Files = new List<string>();
public byte[] Hmac { get; set; }
public byte[][] EncKeys { get; } = Util.CreateJaggedArray<byte[][]>(2, 0x10);
public byte[][] Keys { get; } = Util.CreateJaggedArray<byte[][]>(2, 0x10);
public long Length { get; set; }
public Stream Stream { get; }
private List<Stream> Streams = new List<Stream>();
public Nax0(Keyset keyset, string path, string sdPath)
{
if (Directory.Exists(path))
{
while (true)
{
var partName = Path.Combine(path, $"{Files.Count:D2}");
if (!File.Exists(partName)) break;
Files.Add(partName);
}
}
else if (File.Exists(path))
{
Files.Add(path);
}
else
{
throw new FileNotFoundException("Could not find the input file or directory");
}
foreach (var file in Files)
{
Streams.Add(new FileStream(file, FileMode.Open));
}
var stream = new CombinationStream(Streams);
ReadHeader(stream);
for (int k = 0; k < 2; k++)
{
var naxSpecificKeys = Util.CreateJaggedArray<byte[][]>(2, 0x10);
var hashKey2 = new byte[0x10];
Array.Copy(keyset.sd_card_keys[k], hashKey2, 0x10);
var hash2 = new HMACSHA256(hashKey2);
var sdPathBytes = Encoding.ASCII.GetBytes(sdPath);
var checksum = hash2.ComputeHash(sdPathBytes, 0, sdPathBytes.Length);
Array.Copy(checksum, 0, naxSpecificKeys[0], 0, 0x10);
Array.Copy(checksum, 0x10, naxSpecificKeys[1], 0, 0x10);
Crypto.DecryptEcb(naxSpecificKeys[0], EncKeys[0], Keys[0], 0x10);
Crypto.DecryptEcb(naxSpecificKeys[1], EncKeys[1], Keys[1], 0x10);
}
stream.Position = 0x20;
var hashKey = new byte[0x60];
stream.Read(hashKey, 0, 0x60);
Array.Copy(Keys[0], 0, hashKey, 8, 0x10);
Array.Copy(Keys[1], 0, hashKey, 0x18, 0x10);
var hash = new HMACSHA256(hashKey);
var validationMac = hash.ComputeHash(keyset.sd_card_keys[1], 0x10, 0x10);
var isValid = Util.ArraysEqual(Hmac, validationMac);
if (!isValid) throw new ArgumentException("NAX0 key derivation failed.");
stream.Position = 0x4000;
var xts = XtsAes128.Create(Keys[0], Keys[1]);
Stream = new RandomAccessSectorStream(new XtsSectorStream(stream, xts, 0x4000, 0x4000));
}
private void ReadHeader(Stream nax0)
{
var reader = new BinaryReader(nax0);
nax0.Position = 0;
Hmac = reader.ReadBytes(0x20);
nax0.Position += 8; //todo check magic
EncKeys[0] = reader.ReadBytes(0x10);
EncKeys[1] = reader.ReadBytes(0x10);
Length = reader.ReadInt64();
}
}
}

View file

@ -1,4 +1,4 @@
using System;
using System.IO;
namespace libhac
{
@ -6,7 +6,16 @@ namespace libhac
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
var keyset = ExternalKeys.ReadKeyFile(args[0]);
keyset.SetSdSeed(args[1].ToBytes());
var nax0 = new Nax0(keyset, args[2], args[3]);
using (var output = new FileStream(args[4], FileMode.Create))
using (var progress = new ProgressBar())
{
progress.LogMessage($"Writing {args[4]}");
Util.CopyStream(nax0.Stream, output, nax0.Stream.Length, progress);
}
}
}
}

130
libhac/ProgressBar.cs Normal file
View file

@ -0,0 +1,130 @@
// Adapted from https://gist.github.com/0ab6a96899cc5377bf54
using System;
using System.Text;
using System.Threading;
namespace libhac
{
public class ProgressBar : IDisposable, IProgressReport
{
private const int BlockCount = 20;
private long _progress;
private long _total;
private readonly Timer _timer;
private readonly TimeSpan _animationInterval = TimeSpan.FromSeconds(1.0 / 30);
private const string Animation = @"|/-\";
private string _currentText = string.Empty;
private bool _disposed;
private int _animationIndex;
private StringBuilder LogText { get; } = new StringBuilder();
public ProgressBar()
{
var timerCallBack = new TimerCallback(TimerHandler);
_timer = new Timer(timerCallBack, 0, 0, 0);
}
public void Report(long value)
{
Interlocked.Exchange(ref _progress, value);
}
public void ReportAdd(long value)
{
Interlocked.Add(ref _progress, value);
}
public void LogMessage(string message)
{
lock (_timer)
{
LogText.AppendLine(message);
}
}
public void SetTotal(long value)
{
Interlocked.Exchange(ref _total, value);
Report(0);
}
private void TimerHandler(object state)
{
lock (_timer)
{
if (_disposed) return;
string text = string.Empty;
if (_total > 0)
{
double progress = _total == 0 ? 0 : (double)_progress / _total;
int progressBlockCount = (int)Math.Min(progress * BlockCount, BlockCount);
text = $"[{new string('#', progressBlockCount)}{new string('-', BlockCount - progressBlockCount)}] {_progress}/{_total} {progress:P1} {Animation[_animationIndex++ % Animation.Length]}";
}
UpdateText(text);
ResetTimer();
}
}
private void UpdateText(string text)
{
StringBuilder outputBuilder = new StringBuilder();
if (LogText.Length > 0)
{
// Erase current text
outputBuilder.Append("\r");
outputBuilder.Append(' ', _currentText.Length);
outputBuilder.Append("\r");
outputBuilder.Append(LogText);
_currentText = string.Empty;
LogText.Clear();
}
// Get length of common portion
int commonPrefixLength = 0;
int commonLength = Math.Min(_currentText.Length, text.Length);
while (commonPrefixLength < commonLength && text[commonPrefixLength] == _currentText[commonPrefixLength])
{
commonPrefixLength++;
}
// Backtrack to the first differing character
outputBuilder.Append('\b', _currentText.Length - commonPrefixLength);
// Output new suffix
outputBuilder.Append(text.Substring(commonPrefixLength));
// If the new text is shorter than the old one: delete overlapping characters
int overlapCount = _currentText.Length - text.Length;
if (overlapCount > 0)
{
outputBuilder.Append(' ', overlapCount);
outputBuilder.Append('\b', overlapCount);
}
Console.Write(outputBuilder);
_currentText = text;
}
private void ResetTimer()
{
_timer.Change(_animationInterval, TimeSpan.FromMilliseconds(-1));
}
public void Dispose()
{
lock (_timer)
{
_disposed = true;
UpdateText(string.Empty);
}
}
}
}

140
libhac/Util.cs Normal file
View file

@ -0,0 +1,140 @@
using System;
using System.IO;
namespace libhac
{
public static class Util
{
public static T CreateJaggedArray<T>(params int[] lengths)
{
return (T)InitializeJaggedArray(typeof(T).GetElementType(), 0, lengths);
}
private static object InitializeJaggedArray(Type type, int index, int[] lengths)
{
Array array = Array.CreateInstance(type, lengths[index]);
Type elementType = type.GetElementType();
if (elementType == null) return array;
for (int i = 0; i < lengths[index]; i++)
{
array.SetValue(InitializeJaggedArray(elementType, index + 1, lengths), i);
}
return array;
}
public static bool ArraysEqual<T>(T[] a1, T[] a2)
{
if (a1 == null || a2 == null) return false;
if (a1 == a2) return true;
if (a1.Length != a2.Length) return false;
for (int i = 0; i < a1.Length; i++)
{
if (!a1[i].Equals(a2[i]))
{
return false;
}
}
return true;
}
public static bool IsEmpty(this byte[] array)
{
if (array == null) throw new ArgumentNullException(nameof(array));
for (int i = 0; i < array.Length; i++)
{
if (i != 0)
{
return false;
}
}
return true;
}
public static void CopyStream(this Stream input, Stream output, long length, IProgressReport progress = null)
{
const int bufferSize = 0x8000;
long remaining = length;
byte[] buffer = new byte[bufferSize];
progress?.SetTotal((length + bufferSize) / bufferSize);
int read;
while ((read = input.Read(buffer, 0, (int)Math.Min(buffer.Length, remaining))) > 0)
{
output.Write(buffer, 0, read);
remaining -= read;
progress?.ReportAdd(1);
}
}
private static int HexToInt(char c)
{
switch (c)
{
case '0':
return 0;
case '1':
return 1;
case '2':
return 2;
case '3':
return 3;
case '4':
return 4;
case '5':
return 5;
case '6':
return 6;
case '7':
return 7;
case '8':
return 8;
case '9':
return 9;
case 'a':
case 'A':
return 10;
case 'b':
case 'B':
return 11;
case 'c':
case 'C':
return 12;
case 'd':
case 'D':
return 13;
case 'e':
case 'E':
return 14;
case 'f':
case 'F':
return 15;
default:
throw new FormatException("Unrecognized hex char " + c);
}
}
private static readonly byte[,] ByteLookup = {
{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f},
{0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xa0, 0xb0, 0xc0, 0xd0, 0xe0, 0xf0}
};
public static byte[] ToBytes(this string input)
{
var result = new byte[(input.Length + 1) >> 1];
int lastcell = result.Length - 1;
int lastchar = input.Length - 1;
for (int i = 0; i < input.Length; i++)
{
result[lastcell - (i >> 1)] |= ByteLookup[i & 1, HexToInt(input[lastchar - i])];
}
return result;
}
}
}

View file

@ -0,0 +1,323 @@
// Copyright (c) 2010 Gareth Lennox (garethl@dwakn.com)
// All rights reserved.
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name of Gareth Lennox nor the names of its
// contributors may be used to endorse or promote products derived from this
// software without specific prior written permission.
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
using System;
using System.IO;
namespace libhac.XTSSharp
{
/// <summary>
/// A wraps a sector based stream and provides random access to it
/// </summary>
public class RandomAccessSectorStream : Stream
{
private readonly byte[] _buffer;
private readonly int _bufferSize;
private readonly SectorStream _s;
private readonly bool _isStreamOwned;
private bool _bufferDirty;
private bool _bufferLoaded;
private int _bufferPos;
/// <summary>
/// Creates a new stream
/// </summary>
/// <param name="s">Base stream</param>
public RandomAccessSectorStream(SectorStream s)
: this(s, false)
{
}
/// <summary>
/// Creates a new stream
/// </summary>
/// <param name="s">Base stream</param>
/// <param name="isStreamOwned">Does this stream own the base stream? i.e. should it be automatically disposed?</param>
public RandomAccessSectorStream(SectorStream s, bool isStreamOwned)
{
_s = s;
_isStreamOwned = isStreamOwned;
_buffer = new byte[s.SectorSize];
_bufferSize = s.SectorSize;
}
/// <summary>
/// Gets a value indicating whether the current stream supports reading.
/// </summary>
/// <returns>true if the stream supports reading; otherwise, false.</returns>
public override bool CanRead
{
get { return _s.CanRead; }
}
/// <summary>
/// Gets a value indicating whether the current stream supports seeking.
/// </summary>
/// <returns>true if the stream supports seeking; otherwise, false.</returns>
public override bool CanSeek
{
get { return _s.CanSeek; }
}
/// <summary>
/// Gets a value indicating whether the current stream supports writing.
/// </summary>
/// <returns>true if the stream supports writing; otherwise, false.</returns>
public override bool CanWrite
{
get { return _s.CanWrite; }
}
/// <summary>
/// Gets the length in bytes of the stream.
/// </summary>
/// <returns>A long value representing the length of the stream in bytes.</returns>
public override long Length
{
get { return _s.Length + _bufferPos; }
}
/// <summary>
/// Gets or sets the position within the current stream.
/// </summary>
/// <returns>The current position within the stream.</returns>
public override long Position
{
get { return _bufferLoaded ? (_s.Position - _bufferSize + _bufferPos) : _s.Position + _bufferPos; }
set
{
if (value < 0L)
throw new ArgumentOutOfRangeException("value");
var sectorPosition = (value % _bufferSize);
var position = value - sectorPosition;
//see if its within the current sector
if (_bufferLoaded)
{
var basePosition = _s.Position - _bufferSize;
if (value > basePosition && value < basePosition + _bufferSize)
{
_bufferPos = (int)sectorPosition;
return;
}
}
//outside the current buffer
//write it
if (_bufferDirty)
WriteSector();
_s.Position = position;
//read this sector
ReadSector();
//bump us forward if need be
_bufferPos = (int)sectorPosition;
}
}
/// <summary>
/// Releases the unmanaged resources used by the <see cref="T:System.IO.Stream"/> and optionally releases the managed resources.
/// </summary>
/// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources. </param>
protected override void Dispose(bool disposing)
{
Flush();
base.Dispose(disposing);
if (_isStreamOwned)
_s.Dispose();
}
/// <summary>
/// Clears all buffers for this stream and causes any buffered data to be written to the underlying device.
/// </summary>
public override void Flush()
{
if (_bufferDirty)
WriteSector();
}
/// <summary>
/// Sets the position within the current stream.
/// </summary>
/// <returns>
/// The new position within the current stream.
/// </returns>
/// <param name="offset">A byte offset relative to the <paramref name="origin"/> parameter.</param>
/// <param name="origin">A value of type <see cref="T:System.IO.SeekOrigin"/> indicating the reference point used to obtain the new position.</param>
public override long Seek(long offset, SeekOrigin origin)
{
long newPosition;
switch (origin)
{
case SeekOrigin.Begin:
newPosition = offset;
break;
case SeekOrigin.End:
newPosition = Length - offset;
break;
default:
newPosition = Position + offset;
break;
}
Position = newPosition;
return newPosition;
}
/// <summary>
/// Sets the length of the current stream.
/// </summary>
/// <param name="value">The desired length of the current stream in bytes.</param>
public override void SetLength(long value)
{
var remainder = value % _s.SectorSize;
if (remainder > 0)
{
value = (value - remainder) + _bufferSize;
}
_s.SetLength(value);
}
/// <summary>
/// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read.
/// </summary>
/// <returns>The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached.</returns>
/// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array with the values between <paramref name="offset"/> and (<paramref name="offset"/> + <paramref name="count"/> - 1) replaced by the bytes read from the current source. </param>
/// <param name="offset">The zero-based byte offset in <paramref name="buffer"/> at which to begin storing the data read from the current stream.</param>
/// <param name="count">The maximum number of bytes to be read from the current stream.</param>
public override int Read(byte[] buffer, int offset, int count)
{
var position = Position;
if (position + count > _s.Length)
{
count = (int)(_s.Length - position);
}
if (!_bufferLoaded)
ReadSector();
var totalBytesRead = 0;
while (count > 0)
{
var bytesToRead = Math.Min(count, _bufferSize - _bufferPos);
Buffer.BlockCopy(_buffer, _bufferPos, buffer, offset, bytesToRead);
offset += bytesToRead;
_bufferPos += bytesToRead;
count -= bytesToRead;
totalBytesRead += bytesToRead;
if (_bufferPos == _bufferSize)
ReadSector();
}
return totalBytesRead;
}
/// <summary>
/// Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written.
/// </summary>
/// <param name="buffer">An array of bytes. This method copies <paramref name="count"/> bytes from <paramref name="buffer"/> to the current stream.</param>
/// <param name="offset">The zero-based byte offset in <paramref name="buffer"/> at which to begin copying bytes to the current stream.</param>
/// <param name="count">The number of bytes to be written to the current stream.</param>
public override void Write(byte[] buffer, int offset, int count)
{
while (count > 0)
{
if (!_bufferLoaded)
ReadSector();
var bytesToWrite = Math.Min(count, _bufferSize - _bufferPos);
Buffer.BlockCopy(buffer, offset, _buffer, _bufferPos, bytesToWrite);
offset += bytesToWrite;
_bufferPos += bytesToWrite;
count -= bytesToWrite;
_bufferDirty = true;
if (_bufferPos == _bufferSize)
WriteSector();
}
}
/// <summary>
/// Reads a sector
/// </summary>
private void ReadSector()
{
if (_bufferLoaded && _bufferDirty)
{
WriteSector();
}
if (_s.Position == _s.Length)
{
return;
}
var bytesRead = _s.Read(_buffer, 0, _buffer.Length);
//clean the end of it
if (bytesRead != _bufferSize)
Array.Clear(_buffer, bytesRead, _buffer.Length - bytesRead);
_bufferLoaded = true;
_bufferPos = 0;
_bufferDirty = false;
}
/// <summary>
/// Writes a sector
/// </summary>
private void WriteSector()
{
if (_bufferLoaded)
{
//go back to beginning of the current sector
_s.Seek(-_bufferSize, SeekOrigin.Current);
}
//write it
_s.Write(_buffer, 0, _bufferSize);
_bufferDirty = false;
_bufferLoaded = false;
_bufferPos = 0;
Array.Clear(_buffer, 0, _bufferSize);
}
}
}

View file

@ -0,0 +1,233 @@
// Copyright (c) 2010 Gareth Lennox (garethl@dwakn.com)
// All rights reserved.
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name of Gareth Lennox nor the names of its
// contributors may be used to endorse or promote products derived from this
// software without specific prior written permission.
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
using System;
using System.IO;
namespace libhac.XTSSharp
{
/// <summary>
/// Sector-based stream
/// </summary>
public class SectorStream : Stream
{
private readonly Stream _baseStream;
private readonly long _offset;
private ulong _currentSector;
/// <summary>
/// Creates a new stream
/// </summary>
/// <param name="baseStream">The base stream to read/write from</param>
/// <param name="sectorSize">The size of the sectors to read/write</param>
public SectorStream(Stream baseStream, int sectorSize)
: this(baseStream, sectorSize, 0)
{
}
/// <summary>
/// Creates a new stream
/// </summary>
/// <param name="baseStream">The base stream to read/write from</param>
/// <param name="sectorSize">The size of the sectors to read/write</param>
/// <param name="offset">Offset to start counting sectors</param>
public SectorStream(Stream baseStream, int sectorSize, long offset)
{
SectorSize = sectorSize;
_baseStream = baseStream;
_offset = offset;
}
/// <summary>
/// The size of the sectors
/// </summary>
public int SectorSize { get; private set; }
/// <summary>
/// Gets a value indicating whether the current stream supports reading.
/// </summary>
/// <returns>true if the stream supports reading; otherwise, false.</returns>
public override bool CanRead
{
get { return _baseStream.CanRead; }
}
/// <summary>
/// Gets a value indicating whether the current stream supports seeking.
/// </summary>
/// <returns>true if the stream supports seeking; otherwise, false.</returns>
public override bool CanSeek
{
get { return _baseStream.CanSeek; }
}
/// <summary>
/// Gets a value indicating whether the current stream supports writing.
/// </summary>
/// <returns>true if the stream supports writing; otherwise, false.</returns>
public override bool CanWrite
{
get { return _baseStream.CanWrite; }
}
/// <summary>
/// Gets the length in bytes of the stream.
/// </summary>
/// <returns>A long value representing the length of the stream in bytes.</returns>
public override long Length
{
get { return _baseStream.Length - _offset; }
}
/// <summary>
/// Gets or sets the position within the current stream.
/// </summary>
/// <returns>The current position within the stream.</returns>
public override long Position
{
get { return _baseStream.Position - _offset; }
set
{
ValidateSizeMultiple(value);
//base stream gets the non-tweaked value
_baseStream.Position = value + _offset;
_currentSector = (ulong)(value / SectorSize);
}
}
/// <summary>
/// The current sector this stream is at
/// </summary>
protected ulong CurrentSector => _currentSector;
/// <summary>
/// Validates that the size is a multiple of the sector size
/// </summary>
// ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local
private void ValidateSizeMultiple(long value)
{
if (value % SectorSize != 0)
throw new ArgumentException(string.Format("Value needs to be a multiple of {0}", SectorSize));
}
/// <summary>
/// Validates that the size is equal to the sector size
/// </summary>
protected void ValidateSize(long value)
{
if (value != SectorSize)
throw new ArgumentException(string.Format("Value needs to be {0}", SectorSize));
}
/// <summary>
/// Validates that the size is equal to the sector size
/// </summary>
protected void ValidateSize(int value)
{
if (value != SectorSize)
throw new ArgumentException(string.Format("Value needs to be {0}", SectorSize));
}
/// <summary>
/// Clears all buffers for this stream and causes any buffered data to be written to the underlying device.
/// </summary>
public override void Flush()
{
_baseStream.Flush();
}
/// <summary>
/// Sets the position within the current stream.
/// </summary>
/// <returns>
/// The new position within the current stream.
/// </returns>
/// <param name="offset">A byte offset relative to the <paramref name="origin"/> parameter.</param>
/// <param name="origin">A value of type <see cref="T:System.IO.SeekOrigin"/> indicating the reference point used to obtain the new position.</param>
public override long Seek(long offset, SeekOrigin origin)
{
long newPosition;
switch (origin)
{
case SeekOrigin.Begin:
newPosition = offset;
break;
case SeekOrigin.End:
newPosition = Length - offset;
break;
default:
newPosition = Position + offset;
break;
}
Position = newPosition;
return newPosition;
}
/// <summary>
/// Sets the length of the current stream.
/// </summary>
/// <param name="value">The desired length of the current stream in bytes.</param>
public override void SetLength(long value)
{
ValidateSizeMultiple(value);
_baseStream.SetLength(value);
}
/// <summary>
/// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read.
/// </summary>
/// <returns>The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached.</returns>
/// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array with the values between <paramref name="offset"/> and (<paramref name="offset"/> + <paramref name="count"/> - 1) replaced by the bytes read from the current source. </param>
/// <param name="offset">The zero-based byte offset in <paramref name="buffer"/> at which to begin storing the data read from the current stream.</param>
/// <param name="count">The maximum number of bytes to be read from the current stream.</param>
public override int Read(byte[] buffer, int offset, int count)
{
ValidateSize(count);
var ret = _baseStream.Read(buffer, offset, count);
_currentSector++;
return ret;
}
/// <summary>
/// Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written.
/// </summary>
/// <param name="buffer">An array of bytes. This method copies <paramref name="count"/> bytes from <paramref name="buffer"/> to the current stream.</param>
/// <param name="offset">The zero-based byte offset in <paramref name="buffer"/> at which to begin copying bytes to the current stream.</param>
/// <param name="count">The number of bytes to be written to the current stream.</param>
public override void Write(byte[] buffer, int offset, int count)
{
ValidateSize(count);
_baseStream.Write(buffer, offset, count);
_currentSector++;
}
}
}

117
libhac/XTSSharp/Xts.cs Normal file
View file

@ -0,0 +1,117 @@
// Copyright (c) 2010 Gareth Lennox (garethl@dwakn.com)
// All rights reserved.
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name of Gareth Lennox nor the names of its
// contributors may be used to endorse or promote products derived from this
// software without specific prior written permission.
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
using System;
using System.Security.Cryptography;
namespace libhac.XTSSharp
{
/// <summary>
/// Xts. See <see cref="XtsAes128"/> and <see cref="XtsAes256"/>.
/// </summary>
public class Xts
{
private readonly SymmetricAlgorithm _key1;
private readonly SymmetricAlgorithm _key2;
/// <summary>
/// Creates a new Xts implementation.
/// </summary>
/// <param name="create">Function to create the implementations</param>
/// <param name="key1">Key 1</param>
/// <param name="key2">Key 2</param>
protected Xts(Func<SymmetricAlgorithm> create, byte[] key1, byte[] key2)
{
if (create == null)
throw new ArgumentNullException("create");
if (key1 == null)
throw new ArgumentNullException("key1");
if (key2 == null)
throw new ArgumentNullException("key2");
_key1 = create();
_key2 = create();
if (key1.Length != key2.Length)
throw new ArgumentException("Key lengths don't match");
//set the key sizes
_key1.KeySize = key1.Length * 8;
_key2.KeySize = key2.Length * 8;
//set the keys
_key1.Key = key1;
_key2.Key = key2;
//ecb mode
_key1.Mode = CipherMode.ECB;
_key2.Mode = CipherMode.ECB;
//no padding - we're always going to be writing full blocks
_key1.Padding = PaddingMode.None;
_key2.Padding = PaddingMode.None;
//fixed block size of 128 bits.
_key1.BlockSize = 16 * 8;
_key2.BlockSize = 16 * 8;
}
/// <summary>
/// Creates an xts encryptor
/// </summary>
public XtsCryptoTransform CreateEncryptor()
{
return new XtsCryptoTransform(_key1.CreateEncryptor(), _key2.CreateEncryptor(), false);
}
/// <summary>
/// Creates an xts decryptor
/// </summary>
public XtsCryptoTransform CreateDecryptor()
{
return new XtsCryptoTransform(_key1.CreateDecryptor(), _key2.CreateEncryptor(), true);
}
/// <summary>
/// Verify that the key is of an expected size of bits
/// </summary>
/// <param name="expectedSize">Expected size of the key in bits</param>
/// <param name="key">The key</param>
/// <returns>The key</returns>
/// <exception cref="ArgumentNullException">If the key is null</exception>
/// <exception cref="ArgumentException">If the key length does not match the expected length</exception>
protected static byte[] VerifyKey(int expectedSize, byte[] key)
{
if (key == null)
throw new ArgumentNullException("key");
if (key.Length * 8 != expectedSize)
throw new ArgumentException(string.Format("Expected key length of {0} bits, got {1}", expectedSize, key.Length * 8));
return key;
}
}
}

View file

@ -0,0 +1,82 @@
// Copyright (c) 2010 Gareth Lennox (garethl@dwakn.com)
// All rights reserved.
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name of Gareth Lennox nor the names of its
// contributors may be used to endorse or promote products derived from this
// software without specific prior written permission.
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
using System;
using System.Security.Cryptography;
namespace libhac.XTSSharp
{
/// <summary>
/// XTS-AES-128 implementation
/// </summary>
public class XtsAes128 : Xts
{
private const int KeyLength = 128;
private const int KeyByteLength = KeyLength / 8;
/// <summary>
/// Creates a new instance
/// </summary>
protected XtsAes128(Func<SymmetricAlgorithm> create, byte[] key1, byte[] key2)
: base(create, VerifyKey(KeyLength, key1), VerifyKey(KeyLength, key2))
{
}
/// <summary>
/// Creates a new implementation
/// </summary>
/// <param name="key1">First key</param>
/// <param name="key2">Second key</param>
/// <returns>Xts implementation</returns>
/// <remarks>Keys need to be 128 bits long (i.e. 16 bytes)</remarks>
public static Xts Create(byte[] key1, byte[] key2)
{
VerifyKey(KeyLength, key1);
VerifyKey(KeyLength, key2);
return new XtsAes128(Aes.Create, key1, key2);
}
/// <summary>
/// Creates a new implementation
/// </summary>
/// <param name="key">Key to use</param>
/// <returns>Xts implementation</returns>
/// <remarks>Key need to be 256 bits long (i.e. 32 bytes)</remarks>
public static Xts Create(byte[] key)
{
VerifyKey(KeyLength * 2, key);
byte[] key1 = new byte[KeyByteLength];
byte[] key2 = new byte[KeyByteLength];
Buffer.BlockCopy(key, 0, key1, 0, KeyByteLength);
Buffer.BlockCopy(key, KeyByteLength, key2, 0, KeyByteLength);
return new XtsAes128(Aes.Create, key1, key2);
}
}
}

View file

@ -0,0 +1,82 @@
// Copyright (c) 2010 Gareth Lennox (garethl@dwakn.com)
// All rights reserved.
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name of Gareth Lennox nor the names of its
// contributors may be used to endorse or promote products derived from this
// software without specific prior written permission.
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
using System;
using System.Security.Cryptography;
namespace libhac.XTSSharp
{
/// <summary>
/// XTS-AES-256 implementation
/// </summary>
public class XtsAes256 : Xts
{
private const int KeyLength = 256;
private const int KeyByteLength = KeyLength / 8;
/// <summary>
/// Creates a new instance
/// </summary>
protected XtsAes256(Func<SymmetricAlgorithm> create, byte[] key1, byte[] key2)
: base(create, VerifyKey(KeyLength, key1), VerifyKey(KeyLength, key2))
{
}
/// <summary>
/// Creates a new implementation
/// </summary>
/// <param name="key1">First key</param>
/// <param name="key2">Second key</param>
/// <returns>Xts implementation</returns>
/// <remarks>Keys need to be 256 bits long (i.e. 32 bytes)</remarks>
public static Xts Create(byte[] key1, byte[] key2)
{
VerifyKey(KeyLength, key1);
VerifyKey(KeyLength, key2);
return new XtsAes256(Aes.Create, key1, key2);
}
/// <summary>
/// Creates a new implementation
/// </summary>
/// <param name="key">Key to use</param>
/// <returns>Xts implementation</returns>
/// <remarks>Keys need to be 512 bits long (i.e. 64 bytes)</remarks>
public static Xts Create(byte[] key)
{
VerifyKey(KeyLength * 2, key);
var key1 = new byte[KeyByteLength];
var key2 = new byte[KeyByteLength];
Buffer.BlockCopy(key, 0, key1, 0, KeyByteLength);
Buffer.BlockCopy(key, KeyByteLength, key2, 0, KeyByteLength);
return new XtsAes256(Aes.Create, key1, key2);
}
}
}

View file

@ -0,0 +1,231 @@
// Copyright (c) 2010 Gareth Lennox (garethl@dwakn.com)
// All rights reserved.
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name of Gareth Lennox nor the names of its
// contributors may be used to endorse or promote products derived from this
// software without specific prior written permission.
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
using System;
using System.Security.Cryptography;
namespace libhac.XTSSharp
{
/// <summary>
/// The actual Xts cryptography transform
/// </summary>
/// <remarks>
/// The reason that it doesn't implement ICryptoTransform, as the interface is different.
///
/// Most of the logic was taken from the LibTomCrypt project - http://libtom.org and
/// converted to C#
/// </remarks>
public class XtsCryptoTransform : IDisposable
{
private readonly byte[] _cc = new byte[16];
private readonly bool _decrypting;
private readonly ICryptoTransform _key1;
private readonly ICryptoTransform _key2;
private readonly byte[] _pp = new byte[16];
private readonly byte[] _t = new byte[16];
private readonly byte[] _tweak = new byte[16];
/// <summary>
/// Creates a new transform
/// </summary>
/// <param name="key1">Transform 1</param>
/// <param name="key2">Transform 2</param>
/// <param name="decrypting">Is this a decryption transform?</param>
public XtsCryptoTransform(ICryptoTransform key1, ICryptoTransform key2, bool decrypting)
{
if (key1 == null)
throw new ArgumentNullException("key1");
if (key2 == null)
throw new ArgumentNullException("key2");
_key1 = key1;
_key2 = key2;
_decrypting = decrypting;
}
#region IDisposable Members
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
/// <filterpriority>2</filterpriority>
public void Dispose()
{
_key1.Dispose();
_key2.Dispose();
}
#endregion
/// <summary>
/// Transforms a single block.
/// </summary>
/// <param name="inputBuffer"> The input for which to compute the transform.</param>
/// <param name="inputOffset">The offset into the input byte array from which to begin using data.</param>
/// <param name="inputCount">The number of bytes in the input byte array to use as data.</param>
/// <param name="outputBuffer">The output to which to write the transform.</param>
/// <param name="outputOffset">The offset into the output byte array from which to begin writing data.</param>
/// <param name="sector">The sector number of the block</param>
/// <returns>The number of bytes written.</returns>
public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset, ulong sector)
{
//Nintendo Switch uses Little Endian
FillArrayFromSectorLittleEndian(_tweak, sector);
int lim;
/* get number of blocks */
var m = inputCount >> 4;
var mo = inputCount & 15;
/* encrypt the tweak */
_key2.TransformBlock(_tweak, 0, _tweak.Length, _t, 0);
/* for i = 0 to m-2 do */
if (mo == 0)
lim = m;
else
lim = m - 1;
for (var i = 0; i < lim; i++)
{
TweakCrypt(inputBuffer, inputOffset, outputBuffer, outputOffset, _t);
inputOffset += 16;
outputOffset += 16;
}
/* if ptlen not divide 16 then */
if (mo > 0)
{
if (_decrypting)
{
Buffer.BlockCopy(_t, 0, _cc, 0, 16);
MultiplyByX(_cc);
/* CC = tweak encrypt block m-1 */
TweakCrypt(inputBuffer, inputOffset, _pp, 0, _cc);
/* Cm = first ptlen % 16 bytes of CC */
int i;
for (i = 0; i < mo; i++)
{
_cc[i] = inputBuffer[16 + i + inputOffset];
outputBuffer[16 + i + outputOffset] = _pp[i];
}
for (; i < 16; i++)
{
_cc[i] = _pp[i];
}
/* Cm-1 = Tweak encrypt PP */
TweakCrypt(_cc, 0, outputBuffer, outputOffset, _t);
}
else
{
/* CC = tweak encrypt block m-1 */
TweakCrypt(inputBuffer, inputOffset, _cc, 0, _t);
/* Cm = first ptlen % 16 bytes of CC */
int i;
for (i = 0; i < mo; i++)
{
_pp[i] = inputBuffer[16 + i + inputOffset];
outputBuffer[16 + i + outputOffset] = _cc[i];
}
for (; i < 16; i++)
{
_pp[i] = _cc[i];
}
/* Cm-1 = Tweak encrypt PP */
TweakCrypt(_pp, 0, outputBuffer, outputOffset, _t);
}
}
return inputCount;
}
/// <summary>
/// Fills a byte array from a sector number (little endian)
/// </summary>
/// <param name="value">The destination</param>
/// <param name="sector">The sector number</param>
private static void FillArrayFromSectorLittleEndian(byte[] value, ulong sector)
{
value[0x8] = (byte)((sector >> 56) & 255);
value[0x9] = (byte)((sector >> 48) & 255);
value[0xA] = (byte)((sector >> 40) & 255);
value[0xB] = (byte)((sector >> 32) & 255);
value[0xC] = (byte)((sector >> 24) & 255);
value[0xD] = (byte)((sector >> 16) & 255);
value[0xE] = (byte)((sector >> 8) & 255);
value[0xF] = (byte)(sector & 255);
}
/// <summary>
/// Performs the Xts TweakCrypt operation
/// </summary>
private void TweakCrypt(byte[] inputBuffer, int inputOffset, byte[] outputBuffer, int outputOffset, byte[] t)
{
for (var x = 0; x < 16; x++)
{
outputBuffer[x + outputOffset] = (byte)(inputBuffer[x + inputOffset] ^ t[x]);
}
_key1.TransformBlock(outputBuffer, outputOffset, 16, outputBuffer, outputOffset);
for (var x = 0; x < 16; x++)
{
outputBuffer[x + outputOffset] = (byte)(outputBuffer[x + outputOffset] ^ t[x]);
}
MultiplyByX(t);
}
/// <summary>
/// Multiply by x
/// </summary>
/// <param name="i">The value to multiply by x (LFSR shift)</param>
private static void MultiplyByX(byte[] i)
{
byte t = 0, tt = 0;
for (var x = 0; x < 16; x++)
{
tt = (byte)(i[x] >> 7);
i[x] = (byte)(((i[x] << 1) | t) & 0xFF);
t = tt;
}
if (tt > 0)
i[0] ^= 0x87;
}
}
}

View file

@ -0,0 +1,155 @@
// Copyright (c) 2010 Gareth Lennox (garethl@dwakn.com)
// All rights reserved.
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name of Gareth Lennox nor the names of its
// contributors may be used to endorse or promote products derived from this
// software without specific prior written permission.
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
using System.IO;
namespace libhac.XTSSharp
{
/// <summary>
/// Xts sector-based
/// </summary>
public class XtsSectorStream : SectorStream
{
/// <summary>
/// The default sector size
/// </summary>
public const int DefaultSectorSize = 512;
private readonly byte[] _tempBuffer;
private readonly Xts _xts;
private XtsCryptoTransform _decryptor;
private XtsCryptoTransform _encryptor;
/// <summary>
/// Creates a new stream with the default sector size
/// </summary>
/// <param name="baseStream">The base stream</param>
/// <param name="xts">The xts transform</param>
public XtsSectorStream(Stream baseStream, Xts xts)
: this(baseStream, xts, DefaultSectorSize)
{
}
/// <summary>
/// Creates a new stream
/// </summary>
/// <param name="baseStream">The base stream</param>
/// <param name="xts">The xts transform</param>
/// <param name="sectorSize">Sector size</param>
public XtsSectorStream(Stream baseStream, Xts xts, int sectorSize)
: this(baseStream, xts, sectorSize, 0)
{
}
/// <summary>
/// Creates a new stream
/// </summary>
/// <param name="baseStream">The base stream</param>
/// <param name="xts">The xts transform</param>
/// <param name="sectorSize">Sector size</param>
/// <param name="offset">Offset to start counting sectors</param>
public XtsSectorStream(Stream baseStream, Xts xts, int sectorSize, long offset)
: base(baseStream, sectorSize, offset)
{
_xts = xts;
_tempBuffer = new byte[sectorSize];
}
/// <summary>
/// Releases the unmanaged resources used by the <see cref="T:System.IO.Stream"/> and optionally releases the managed resources.
/// </summary>
/// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (_encryptor != null)
_encryptor.Dispose();
if (_decryptor != null)
_decryptor.Dispose();
}
/// <summary>
/// Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written.
/// </summary>
/// <param name="buffer">An array of bytes. This method copies <paramref name="count"/> bytes from <paramref name="buffer"/> to the current stream.</param>
/// <param name="offset">The zero-based byte offset in <paramref name="buffer"/> at which to begin copying bytes to the current stream.</param>
/// <param name="count">The number of bytes to be written to the current stream.</param>
public override void Write(byte[] buffer, int offset, int count)
{
ValidateSize(count);
if (count == 0)
return;
//get the current sector
var currentSector = CurrentSector;
if (_encryptor == null)
_encryptor = _xts.CreateEncryptor();
//encrypt the sector
int transformedCount = _encryptor.TransformBlock(buffer, offset, count, _tempBuffer, 0, currentSector);
//Console.WriteLine("Encrypting sector {0}", currentSector);
//write it to the base stream
base.Write(_tempBuffer, 0, transformedCount);
}
/// <summary>
/// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read.
/// </summary>
/// <returns>The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached.</returns>
/// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array with the values between <paramref name="offset"/> and (<paramref name="offset"/> + <paramref name="count"/> - 1) replaced by the bytes read from the current source. </param>
/// <param name="offset">The zero-based byte offset in <paramref name="buffer"/> at which to begin storing the data read from the current stream.</param>
/// <param name="count">The maximum number of bytes to be read from the current stream.</param>
public override int Read(byte[] buffer, int offset, int count)
{
ValidateSize(count);
//get the current sector
var currentSector = CurrentSector;
//read the sector from the base stream
var ret = base.Read(_tempBuffer, 0, count);
if (ret == 0)
return 0;
if (_decryptor == null)
_decryptor = _xts.CreateDecryptor();
//decrypt the sector
var retV = _decryptor.TransformBlock(_tempBuffer, 0, ret, buffer, offset, currentSector);
//Console.WriteLine("Decrypting sector {0} == {1} bytes", currentSector, retV);
return retV;
}
}
}

View file

@ -0,0 +1,70 @@
// Copyright (c) 2010 Gareth Lennox (garethl@dwakn.com)
// All rights reserved.
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name of Gareth Lennox nor the names of its
// contributors may be used to endorse or promote products derived from this
// software without specific prior written permission.
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
using System.IO;
namespace libhac.XTSSharp
{
/// <summary>
/// A random access, xts encrypted stream
/// </summary>
public class XtsStream : RandomAccessSectorStream
{
/// <summary>
/// Creates a new stream
/// </summary>
/// <param name="baseStream">The base stream</param>
/// <param name="xts">Xts implementation to use</param>
public XtsStream(Stream baseStream, Xts xts)
: this(baseStream, xts, XtsSectorStream.DefaultSectorSize)
{
}
/// <summary>
/// Creates a new stream
/// </summary>
/// <param name="baseStream">The base stream</param>
/// <param name="xts">Xts implementation to use</param>
/// <param name="sectorSize">Sector size</param>
public XtsStream(Stream baseStream, Xts xts, int sectorSize)
: base(new XtsSectorStream(baseStream, xts, sectorSize), true)
{
}
/// <summary>
/// Creates a new stream
/// </summary>
/// <param name="baseStream">The base stream</param>
/// <param name="xts">Xts implementation to use</param>
/// <param name="sectorSize">Sector size</param>
/// <param name="offset">Offset to start counting sectors</param>
public XtsStream(Stream baseStream, Xts xts, int sectorSize, long offset)
: base(new XtsSectorStream(baseStream, xts, sectorSize, offset), true)
{
}
}
}

View file

@ -1,8 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<LangVersion>7.3</LangVersion>
</PropertyGroup>
</Project>