2023-01-22 04:02:33 +00:00
using System ;
2017-11-06 11:24:48 +00:00
using System.Security.Cryptography ;
2022-01-03 05:35:59 +00:00
using static System . Buffers . Binary . BinaryPrimitives ;
2017-11-06 11:24:48 +00:00
2022-06-18 18:04:24 +00:00
namespace PKHeX.Core ;
/// <summary>
/// MemeCrypto V1 - The Original Series
/// </summary>
/// <remarks>
/// A variant of <see cref="SaveFile"/> encryption and obfuscation used in <see cref="GameVersion.Gen7"/>.
/// <br> The save file stores a dedicated block to contain a hash of the savedata, computed when the block is zeroed. </br>
/// <br> This signing logic is reused for other authentication; refer to <see cref="MemeKeyIndex"/>. </br>
/// <br> The save file first computes a SHA256 Hash over the block checksum region.
/// The logic then applies a SHA1 hash over the SHA256 hash result, encrypts it with a <see cref="MemeKey"/>, and signs it with an RSA private key in a non-straightforward manner. </br>
/// </remarks>
public static class MemeCrypto
2017-11-06 11:24:48 +00:00
{
2022-06-18 18:04:24 +00:00
private const uint POKE = 0x454B4F50 ;
2017-11-06 11:24:48 +00:00
2022-06-18 18:04:24 +00:00
public static bool VerifyMemePOKE ( ReadOnlySpan < byte > input , out byte [ ] output )
{
2023-01-22 04:02:33 +00:00
if ( input . Length < MemeKey . SignatureLength )
2022-06-18 18:04:24 +00:00
throw new ArgumentException ( "Invalid POKE buffer!" ) ;
var memeLen = input . Length - 8 ;
var memeIndex = MemeKeyIndex . PokedexAndSaveFile ;
for ( var i = input . Length - 8 ; i > = 0 ; i - - )
2017-11-06 11:24:48 +00:00
{
2022-06-18 18:04:24 +00:00
if ( ReadUInt32LittleEndian ( input [ i . . ] ) ! = POKE )
continue ;
2017-11-06 11:24:48 +00:00
2022-06-18 18:04:24 +00:00
var keyIndex = ReadInt32LittleEndian ( input [ ( i + 4 ) . . ] ) ;
if ( ! MemeKey . IsValidPokeKeyIndex ( keyIndex ) )
continue ;
2017-11-06 11:24:48 +00:00
2022-06-18 18:04:24 +00:00
memeLen = i ;
memeIndex = ( MemeKeyIndex ) keyIndex ;
break ;
2017-11-06 11:24:48 +00:00
}
2022-06-18 18:04:24 +00:00
foreach ( var len in new [ ] { memeLen , memeLen - 2 } ) // Account for Pokédex QR Edge case
2017-11-06 11:24:48 +00:00
{
2023-01-22 04:02:33 +00:00
if ( VerifyMemeData ( input [ . . len ] , out output , memeIndex ) )
2021-01-07 07:30:30 +00:00
return true ;
2018-05-12 19:28:48 +00:00
2023-01-22 04:02:33 +00:00
if ( VerifyMemeData ( input [ . . len ] , out output , MemeKeyIndex . PokedexAndSaveFile ) )
2022-06-18 18:04:24 +00:00
return true ;
2017-11-06 11:24:48 +00:00
}
2022-06-18 18:04:24 +00:00
output = Array . Empty < byte > ( ) ;
return false ;
}
public static bool VerifyMemeData ( ReadOnlySpan < byte > input , out byte [ ] output )
{
foreach ( MemeKeyIndex keyIndex in Enum . GetValues ( typeof ( MemeKeyIndex ) ) )
2021-01-07 07:30:30 +00:00
{
2022-06-18 18:04:24 +00:00
if ( VerifyMemeData ( input , out output , keyIndex ) )
return true ;
2021-01-07 07:30:30 +00:00
}
2022-06-18 18:04:24 +00:00
output = Array . Empty < byte > ( ) ;
return false ;
}
2021-01-07 07:30:30 +00:00
2022-06-18 18:04:24 +00:00
public static bool VerifyMemeData ( ReadOnlySpan < byte > input , out byte [ ] output , MemeKeyIndex keyIndex )
{
2023-01-22 04:02:33 +00:00
if ( input . Length < MemeKey . SignatureLength )
2017-11-06 11:24:48 +00:00
{
2022-01-03 05:35:59 +00:00
output = Array . Empty < byte > ( ) ;
2017-11-06 11:24:48 +00:00
return false ;
}
2022-06-18 18:04:24 +00:00
var key = new MemeKey ( keyIndex ) ;
2023-01-22 04:02:33 +00:00
Span < byte > sigBuffer = stackalloc byte [ MemeKey . SignatureLength ] ;
var inputSig = input [ ^ MemeKey . SignatureLength . . ] ;
key . RsaPublic ( inputSig , sigBuffer ) ;
2022-06-18 18:04:24 +00:00
2023-01-22 04:02:33 +00:00
output = input . ToArray ( ) ;
if ( DecryptCompare ( output , sigBuffer , key ) )
2022-06-18 18:04:24 +00:00
return true ;
2023-01-22 04:02:33 +00:00
2022-06-18 18:04:24 +00:00
sigBuffer [ 0x0 ] | = 0x80 ;
2023-01-22 04:02:33 +00:00
output = input . ToArray ( ) ;
if ( DecryptCompare ( output , sigBuffer , key ) )
2022-06-18 18:04:24 +00:00
return true ;
output = Array . Empty < byte > ( ) ;
return false ;
}
2017-11-06 11:24:48 +00:00
2023-01-22 04:02:33 +00:00
private static bool DecryptCompare ( Span < byte > output , ReadOnlySpan < byte > sigBuffer , MemeKey key )
2022-06-18 18:04:24 +00:00
{
2023-01-22 04:02:33 +00:00
sigBuffer . CopyTo ( output [ ^ MemeKey . SignatureLength . . ] ) ;
key . AesDecrypt ( output ) ;
2022-06-18 18:04:24 +00:00
// Check for 8-byte equality.
2023-01-22 04:02:33 +00:00
Span < byte > hash = stackalloc byte [ SHA1 . HashSizeInBytes ] ;
SHA1 . HashData ( output [ . . ^ 8 ] , hash ) ;
return hash [ . . 8 ] . SequenceEqual ( output [ ^ 8. . ] ) ;
2022-06-18 18:04:24 +00:00
}
2017-11-06 11:24:48 +00:00
2022-06-18 18:04:24 +00:00
public static bool VerifyMemeData ( ReadOnlySpan < byte > input , out byte [ ] output , int offset , int length , MemeKeyIndex keyIndex )
{
var data = input . Slice ( offset , length ) ;
if ( VerifyMemeData ( data , out output , keyIndex ) )
2017-11-06 11:24:48 +00:00
{
2022-06-18 18:04:24 +00:00
var newOutput = input . ToArray ( ) ;
output . CopyTo ( newOutput , offset ) ;
output = newOutput ;
return true ;
2017-11-06 11:24:48 +00:00
}
2022-06-18 18:04:24 +00:00
output = Array . Empty < byte > ( ) ;
return false ;
}
2017-11-06 11:24:48 +00:00
2022-06-18 18:04:24 +00:00
public static byte [ ] SignMemeData ( ReadOnlySpan < byte > input , MemeKeyIndex keyIndex = MemeKeyIndex . PokedexAndSaveFile )
2023-01-22 04:02:33 +00:00
{
var output = input . ToArray ( ) ;
SignMemeDataInPlace ( output , keyIndex ) ;
return output ;
}
private static void SignMemeDataInPlace ( Span < byte > data , MemeKeyIndex keyIndex = MemeKeyIndex . PokedexAndSaveFile )
2022-06-18 18:04:24 +00:00
{
// Validate Input
2023-01-22 04:02:33 +00:00
if ( data . Length < MemeKey . SignatureLength )
throw new ArgumentException ( "Cannot sign a buffer less than 0x60 bytes in size!" ) ;
2022-06-18 18:04:24 +00:00
var key = new MemeKey ( keyIndex ) ;
if ( ! key . CanResign )
2023-01-22 04:02:33 +00:00
throw new ArgumentException ( "Cannot sign with the specified key!" ) ;
2022-06-18 18:04:24 +00:00
2023-01-22 04:02:33 +00:00
// SHA1 the entire payload except for the last 8 bytes; store the first 8 bytes of hash at the end of the input.
var payload = data [ . . ^ 8 ] ;
var hash = data [ ^ 8. . ] ;
Span < byte > tmp = stackalloc byte [ SHA1 . HashSizeInBytes ] ;
SHA1 . HashData ( payload , tmp ) ;
2022-06-18 18:04:24 +00:00
// Copy in the SHA1 signature
2023-01-22 04:02:33 +00:00
tmp [ . . hash . Length ] . CopyTo ( hash ) ;
2022-06-18 18:04:24 +00:00
// Perform AES operations
2023-01-22 04:02:33 +00:00
key . AesEncrypt ( data ) ;
var sigBuffer = data [ ^ MemeKey . SignatureLength . . ] ;
sigBuffer [ 0 ] & = 0x7F ; // chop off sign bit
key . RsaPrivate ( sigBuffer , sigBuffer ) ;
2022-06-18 18:04:24 +00:00
}
2017-11-06 11:24:48 +00:00
2023-01-22 04:02:33 +00:00
public const int SaveFileSignatureOffset = 0x100 ;
public const int SaveFileSignatureLength = 0x80 ;
2022-06-18 18:04:24 +00:00
/// <summary>
/// Resigns save data.
/// </summary>
2023-01-22 04:02:33 +00:00
/// <param name="span">Save file data to resign</param>
2022-06-18 18:04:24 +00:00
/// <returns>The resigned save data. Invalid input returns null.</returns>
2023-01-22 04:02:33 +00:00
public static void SignInPlace ( Span < byte > span )
2022-06-18 18:04:24 +00:00
{
2023-01-22 04:02:33 +00:00
if ( span . Length is not ( SaveUtil . SIZE_G7SM or SaveUtil . SIZE_G7USUM ) )
2022-06-18 18:04:24 +00:00
throw new ArgumentException ( "Should not be using this for unsupported saves." ) ;
2017-11-06 11:24:48 +00:00
2023-01-22 04:02:33 +00:00
var isUSUM = span . Length = = SaveUtil . SIZE_G7USUM ;
var ChecksumTableOffset = span . Length - 0x200 ;
2022-06-18 18:04:24 +00:00
var ChecksumSignatureLength = isUSUM ? 0x150 : 0x140 ;
2023-01-22 04:02:33 +00:00
var MemeCryptoOffset = ( isUSUM ? 0x6C000 : 0x6BA00 ) + SaveFileSignatureOffset ;
2017-11-06 11:24:48 +00:00
2023-01-22 04:02:33 +00:00
// data[0x80]. Region is initially zero when exporting (nothing set).
// Store a SHA256[0x20] hash of checksums at [..0x20].
// Compute the signature over this 0x80 region, store at [0x20..]
var sigSpan = span . Slice ( MemeCryptoOffset , SaveFileSignatureLength ) ;
var chkBlockSpan = span . Slice ( ChecksumTableOffset , ChecksumSignatureLength ) ;
2017-11-06 11:24:48 +00:00
2023-01-22 04:02:33 +00:00
SignInPlace ( sigSpan , chkBlockSpan ) ;
}
2022-06-18 18:04:24 +00:00
2023-01-22 04:02:33 +00:00
public static void SignInPlace ( Span < byte > sigSpan , ReadOnlySpan < byte > chkBlockSpan )
{
SHA256 . HashData ( chkBlockSpan , sigSpan ) ;
SignMemeDataInPlace ( sigSpan ) ;
2017-11-06 11:24:48 +00:00
}
}