ui基本完毕,修了一大把的bug
This commit is contained in:
@@ -27,7 +27,7 @@ public static class SecureSaveVault
|
||||
|
||||
private static string VaultDirectoryPath
|
||||
{
|
||||
get { return Path.Combine(Application.persistentDataPath, VaultDirectoryName); }
|
||||
get { return Path.Combine(SaveIdentityUtility.GetCanonicalPersistentRoot(), VaultDirectoryName); }
|
||||
}
|
||||
|
||||
public static bool SaveJson<T>(string category, string key, T data, string legacyPlainPath = null)
|
||||
@@ -121,20 +121,11 @@ public static class SecureSaveVault
|
||||
return false;
|
||||
}
|
||||
|
||||
string mainPath = GetFilePath(category, key, ".dat");
|
||||
string backupPath = GetFilePath(category, key, ".bak");
|
||||
|
||||
if (TryReadEncryptedFile(category, key, mainPath, out json))
|
||||
if (TryLoadFromCurrentOrLegacyEncryptedFiles(category, key, legacyPlainPath, out json))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (TryReadEncryptedFile(category, key, backupPath, out json))
|
||||
{
|
||||
SaveRawJson(category, key, json, legacyPlainPath);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(legacyPlainPath) && File.Exists(legacyPlainPath))
|
||||
{
|
||||
try
|
||||
@@ -262,8 +253,7 @@ public static class SecureSaveVault
|
||||
return false;
|
||||
}
|
||||
|
||||
string expectedSignature = ComputeSignature(category, envelope.payload);
|
||||
if (!string.Equals(expectedSignature, envelope.signature, StringComparison.Ordinal))
|
||||
if (!TryValidateSignature(category, envelope.payload, envelope.signature))
|
||||
{
|
||||
Debug.LogWarning($"[SecureSaveVault] Signature mismatch ({category}/{key}). Possible tampering detected.");
|
||||
return false;
|
||||
@@ -329,7 +319,7 @@ public static class SecureSaveVault
|
||||
|
||||
private static string ComputeSignature(string category, string payloadBase64)
|
||||
{
|
||||
string signText = payloadBase64 + "|" + category + "|" + Application.identifier + "|" + SecretSeed + "|" + SystemInfo.deviceUniqueIdentifier;
|
||||
string signText = payloadBase64 + "|" + category + "|" + SaveIdentityUtility.GetPrimaryApplicationIdentifier() + "|" + SecretSeed + "|" + SystemInfo.deviceUniqueIdentifier;
|
||||
using (var sha = SHA256.Create())
|
||||
{
|
||||
byte[] hash = sha.ComputeHash(Encoding.UTF8.GetBytes(signText));
|
||||
@@ -350,7 +340,7 @@ public static class SecureSaveVault
|
||||
{
|
||||
aes.Mode = CipherMode.CBC;
|
||||
aes.Padding = PaddingMode.PKCS7;
|
||||
aes.Key = BuildAesKey(category, key);
|
||||
aes.Key = BuildAesKey(category, key, SaveIdentityUtility.GetPrimaryApplicationIdentifier());
|
||||
aes.GenerateIV();
|
||||
using (var encryptor = aes.CreateEncryptor())
|
||||
{
|
||||
@@ -374,28 +364,44 @@ public static class SecureSaveVault
|
||||
return plainBytes != null;
|
||||
}
|
||||
#endif
|
||||
using (var aes = Aes.Create())
|
||||
IReadOnlyList<string> identifierVariants = SaveIdentityUtility.GetApplicationIdentifierVariants();
|
||||
for (int i = 0; i < identifierVariants.Count; i++)
|
||||
{
|
||||
aes.Mode = CipherMode.CBC;
|
||||
aes.Padding = PaddingMode.PKCS7;
|
||||
aes.Key = BuildAesKey(category, key);
|
||||
int ivLength = aes.BlockSize / 8;
|
||||
if (protectedBytes == null || protectedBytes.Length <= ivLength)
|
||||
using (var aes = Aes.Create())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
aes.Mode = CipherMode.CBC;
|
||||
aes.Padding = PaddingMode.PKCS7;
|
||||
aes.Key = BuildAesKey(category, key, identifierVariants[i]);
|
||||
int ivLength = aes.BlockSize / 8;
|
||||
if (protectedBytes == null || protectedBytes.Length <= ivLength)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
byte[] iv = new byte[ivLength];
|
||||
byte[] cipher = new byte[protectedBytes.Length - ivLength];
|
||||
Buffer.BlockCopy(protectedBytes, 0, iv, 0, ivLength);
|
||||
Buffer.BlockCopy(protectedBytes, ivLength, cipher, 0, cipher.Length);
|
||||
aes.IV = iv;
|
||||
using (var decryptor = aes.CreateDecryptor())
|
||||
{
|
||||
plainBytes = decryptor.TransformFinalBlock(cipher, 0, cipher.Length);
|
||||
return plainBytes != null;
|
||||
byte[] iv = new byte[ivLength];
|
||||
byte[] cipher = new byte[protectedBytes.Length - ivLength];
|
||||
Buffer.BlockCopy(protectedBytes, 0, iv, 0, ivLength);
|
||||
Buffer.BlockCopy(protectedBytes, ivLength, cipher, 0, cipher.Length);
|
||||
aes.IV = iv;
|
||||
try
|
||||
{
|
||||
using (var decryptor = aes.CreateDecryptor())
|
||||
{
|
||||
plainBytes = decryptor.TransformFinalBlock(cipher, 0, cipher.Length);
|
||||
if (plainBytes != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
plainBytes = null;
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -416,7 +422,7 @@ public static class SecureSaveVault
|
||||
|
||||
try
|
||||
{
|
||||
protectedBytes = s_dpapiProtectMethod.Invoke(null, new object[] { plainBytes, BuildEntropy(category), s_dpapiCurrentUserScope }) as byte[];
|
||||
protectedBytes = s_dpapiProtectMethod.Invoke(null, new object[] { plainBytes, BuildEntropy(category, SaveIdentityUtility.GetPrimaryApplicationIdentifier()), s_dpapiCurrentUserScope }) as byte[];
|
||||
return protectedBytes != null && protectedBytes.Length > 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -435,16 +441,24 @@ public static class SecureSaveVault
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
IReadOnlyList<string> identifierVariants = SaveIdentityUtility.GetApplicationIdentifierVariants();
|
||||
for (int i = 0; i < identifierVariants.Count; i++)
|
||||
{
|
||||
plainBytes = s_dpapiUnprotectMethod.Invoke(null, new object[] { protectedBytes, BuildEntropy(category), s_dpapiCurrentUserScope }) as byte[];
|
||||
return plainBytes != null && plainBytes.Length > 0;
|
||||
}
|
||||
catch
|
||||
{
|
||||
plainBytes = null;
|
||||
return false;
|
||||
try
|
||||
{
|
||||
plainBytes = s_dpapiUnprotectMethod.Invoke(null, new object[] { protectedBytes, BuildEntropy(category, identifierVariants[i]), s_dpapiCurrentUserScope }) as byte[];
|
||||
if (plainBytes != null && plainBytes.Length > 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
plainBytes = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool EnsureDpapi()
|
||||
@@ -492,29 +506,116 @@ public static class SecureSaveVault
|
||||
}
|
||||
#endif
|
||||
|
||||
private static byte[] BuildEntropy(string category)
|
||||
private static byte[] BuildEntropy(string category, string applicationIdentifier)
|
||||
{
|
||||
string seed = Application.identifier + "|" + SystemInfo.deviceUniqueIdentifier + "|" + SecretSeed + "|" + category;
|
||||
string seed = applicationIdentifier + "|" + SystemInfo.deviceUniqueIdentifier + "|" + SecretSeed + "|" + category;
|
||||
using (var sha = SHA256.Create())
|
||||
{
|
||||
return sha.ComputeHash(Encoding.UTF8.GetBytes(seed));
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] BuildAesKey(string category, string key)
|
||||
private static byte[] BuildAesKey(string category, string key, string applicationIdentifier)
|
||||
{
|
||||
return BuildEntropy(category);
|
||||
using (var sha = SHA256.Create())
|
||||
{
|
||||
string seed = applicationIdentifier + "|" + SystemInfo.deviceUniqueIdentifier + "|" + SecretSeed + "|" + category + "|" + key;
|
||||
return sha.ComputeHash(Encoding.UTF8.GetBytes(seed));
|
||||
}
|
||||
}
|
||||
|
||||
private static string ShortHash(string value)
|
||||
{
|
||||
using (var sha = SHA256.Create())
|
||||
{
|
||||
byte[] hash = sha.ComputeHash(Encoding.UTF8.GetBytes(value + "|" + Application.identifier + "|" + SecretSeed));
|
||||
byte[] hash = sha.ComputeHash(Encoding.UTF8.GetBytes(value + "|" + SaveIdentityUtility.GetPrimaryApplicationIdentifier() + "|" + SecretSeed));
|
||||
return BitConverter.ToString(hash, 0, 12).Replace("-", string.Empty).ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
|
||||
private static string ShortHash(string value, string applicationIdentifier)
|
||||
{
|
||||
using (var sha = SHA256.Create())
|
||||
{
|
||||
byte[] hash = sha.ComputeHash(Encoding.UTF8.GetBytes(value + "|" + applicationIdentifier + "|" + SecretSeed));
|
||||
return BitConverter.ToString(hash, 0, 12).Replace("-", string.Empty).ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryLoadFromCurrentOrLegacyEncryptedFiles(string category, string key, string legacyPlainPath, out string json)
|
||||
{
|
||||
json = null;
|
||||
IReadOnlyList<string> roots = SaveIdentityUtility.GetPersistentRootVariants(legacyPlainPath);
|
||||
for (int i = 0; i < roots.Count; i++)
|
||||
{
|
||||
string root = roots[i];
|
||||
if (string.IsNullOrWhiteSpace(root))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
IReadOnlyList<string> identifierVariants = SaveIdentityUtility.GetApplicationIdentifierVariants();
|
||||
for (int variantIndex = 0; variantIndex < identifierVariants.Count; variantIndex++)
|
||||
{
|
||||
string applicationIdentifier = identifierVariants[variantIndex];
|
||||
|
||||
string mainPath = GetFilePathForRoot(root, category, key, ".dat", applicationIdentifier);
|
||||
if (TryReadEncryptedFile(category, key, mainPath, out json))
|
||||
{
|
||||
SaveRawJson(category, key, json, legacyPlainPath);
|
||||
return true;
|
||||
}
|
||||
|
||||
string backupPath = GetFilePathForRoot(root, category, key, ".bak", applicationIdentifier);
|
||||
if (TryReadEncryptedFile(category, key, backupPath, out json))
|
||||
{
|
||||
SaveRawJson(category, key, json, legacyPlainPath);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool TryValidateSignature(string category, string payloadBase64, string signature)
|
||||
{
|
||||
IReadOnlyList<string> identifierVariants = SaveIdentityUtility.GetApplicationIdentifierVariants();
|
||||
for (int i = 0; i < identifierVariants.Count; i++)
|
||||
{
|
||||
string expectedSignature = ComputeSignature(category, payloadBase64, identifierVariants[i]);
|
||||
if (string.Equals(expectedSignature, signature, StringComparison.Ordinal))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static string ComputeSignature(string category, string payloadBase64, string applicationIdentifier)
|
||||
{
|
||||
string signText = payloadBase64 + "|" + category + "|" + applicationIdentifier + "|" + SecretSeed + "|" + SystemInfo.deviceUniqueIdentifier;
|
||||
using (var sha = SHA256.Create())
|
||||
{
|
||||
byte[] hash = sha.ComputeHash(Encoding.UTF8.GetBytes(signText));
|
||||
return Convert.ToBase64String(hash);
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetCategoryDirectoryForRoot(string rootPath, string category, string applicationIdentifier)
|
||||
{
|
||||
string safeCategory = ShortHash("cat|" + category, applicationIdentifier);
|
||||
return Path.Combine(rootPath, VaultDirectoryName, "." + safeCategory);
|
||||
}
|
||||
|
||||
private static string GetFilePathForRoot(string rootPath, string category, string key, string extension, string applicationIdentifier)
|
||||
{
|
||||
string categoryDirectory = GetCategoryDirectoryForRoot(rootPath, category, applicationIdentifier);
|
||||
string safeKey = ShortHash("key|" + key, applicationIdentifier);
|
||||
return Path.Combine(categoryDirectory, "." + safeKey + extension);
|
||||
}
|
||||
|
||||
private static void DeleteLegacyPlainFile(string legacyPlainPath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(legacyPlainPath) || !File.Exists(legacyPlainPath))
|
||||
|
||||
Reference in New Issue
Block a user