| r205 vs r206 | ||
|---|---|---|
| ... | ... | |
| 55 | 55 | == csharp 암호화 코드 == |
| 56 | 56 | 아니 1만 8천자 추가된거 이거 맞는건가... |
| 57 | 57 | 휴대폰으로 AI 돌려서 코드 만드니 여기다가 저장해두는 것. 컴퓨터 열어서 유니티랑 VS 키기 귀찮아서 나중에 고치려고 여기가 적는게 맞습니다. |
| 58 | ||
| 59 | === 예전 코드 === | |
| 60 | 아래 신규 코드가 이상하면 이걸 쓰기위해 | |
| 61 | ||
| 62 | {{{#!syntax csharp | |
| 63 | using System; | |
| 64 | using System.IO; | |
| 65 | using System.Security.Cryptography; | |
| 66 | using System.Text; | |
| 67 | using UnityEngine; | |
| 68 | using System.Runtime.CompilerServices; | |
| 69 | ||
| 70 | [Serializable] | |
| 71 | public class PlayerData | |
| 72 | { | |
| 73 | public string playerName; | |
| 74 | public int score; | |
| 75 | } | |
| 76 | ||
| 77 | public class SecureManager : MonoBehaviour | |
| 78 | { | |
| 79 | public static SecureManager Instance { get; private set; } | |
| 80 | ||
| 81 | public bool EnableHardening = true; | |
| 82 | public bool EnableAntiDebugChecks = true; | |
| 83 | public bool EnableRootDetection = true; | |
| 84 | public bool EnableDummyMix = false; | |
| 85 | public bool EnableFileNameObfuscation = true; | |
| 86 | public bool EnableRotation = true; | |
| 87 | ||
| 88 | private const string Magic = "SJMP"; | |
| 89 | private const byte Version = 1; | |
| 90 | private const int SaltLenDefault = 12; | |
| 91 | private const int IvLen = 16; | |
| 92 | private const int HmacLen = 32; | |
| 93 | private const int KeyMaterialLen = 64; | |
| 94 | private const int AesKeyLen = 32; | |
| 95 | private const int HmacKeyLen = 32; | |
| 96 | private const int Pbkdf2Iterations = 20000; | |
| 97 | private readonly byte[] obfPartA = new byte[] { 0x4A, 0x5F, 0x33, 0x29, 0x11, 0x7C, 0x6D, 0x3E, 0x2D, 0x7A, 0x5B, 0x1C }; | |
| 98 | private readonly byte[] obfPartB = new byte[] { 0x91, 0x20, 0x55, 0x12, 0x44, 0x38, 0x77, 0x0A, 0x6F, 0x21, 0x9D, 0xEE }; | |
| 99 | private const byte Mask = 0xAA; | |
| 100 | private string filePath; | |
| 101 | private byte[] sessionNonce; | |
| 102 | ||
| 103 | void Awake() | |
| 104 | { | |
| 105 | if (Instance != null && Instance != this) | |
| 106 | { | |
| 107 | Destroy(gameObject); | |
| 108 | return; | |
| 109 | } | |
| 110 | Instance = this; | |
| 111 | DontDestroyOnLoad(gameObject); | |
| 112 | ||
| 113 | sessionNonce = LoadOrCreateSessionNonce(); | |
| 114 | filePath = Path.Combine(Application.persistentDataPath, ResolveFileName("playerData")); | |
| 115 | Warmup(); | |
| 116 | } | |
| 117 | ||
| 118 | private byte[] LoadOrCreateSessionNonce() | |
| 119 | { | |
| 120 | const string key = "SecureManager_SessionNonce_v1"; | |
| 121 | try | |
| 122 | { | |
| 123 | if (PlayerPrefs.HasKey(key)) | |
| 124 | { | |
| 125 | string b64 = PlayerPrefs.GetString(key); | |
| 126 | byte[] stored = Convert.FromBase64String(b64); | |
| 127 | if (stored != null && stored.Length > 0) return stored; | |
| 128 | } | |
| 129 | } | |
| 130 | catch { } | |
| 131 | ||
| 132 | byte[] nonce = GenerateRandomBytes(16); | |
| 133 | try | |
| 134 | { | |
| 135 | PlayerPrefs.SetString(key, Convert.ToBase64String(nonce)); | |
| 136 | PlayerPrefs.Save(); | |
| 137 | } | |
| 138 | catch { } | |
| 139 | return nonce; | |
| 140 | } | |
| 141 | ||
| 142 | private void Warmup() | |
| 143 | { | |
| 144 | try { _ = GetSecretBytes(); } catch { } | |
| 145 | } | |
| 146 | ||
| 147 | [MethodImpl(MethodImplOptions.NoInlining)] | |
| 148 | private byte[] DumbMixA(byte[] input) | |
| 149 | { | |
| 150 | byte[] outb = new byte[input.Length]; | |
| 151 | for (int i = 0; i < input.Length; i++) | |
| 152 | { | |
| 153 | int v = input[i]; | |
| 154 | v = ((v << 3) | (v >> 5)) & 0xFF; | |
| 155 | v ^= (i * 37) & 0xFF; | |
| 156 | outb[i] = (byte)v; | |
| 157 | } | |
| 158 | return outb; | |
| 159 | } | |
| 160 | ||
| 161 | [MethodImpl(MethodImplOptions.NoInlining)] | |
| 162 | private byte[] DumbMixB(byte[] input, int seed) | |
| 163 | { | |
| 164 | byte[] outb = new byte[input.Length]; | |
| 165 | for (int i = 0; i < input.Length; i++) | |
| 166 | { | |
| 167 | int v = input[i] ^ (seed >> (i % 8)); | |
| 168 | v = (v * 13 + (i * 7)) & 0xFF; | |
| 169 | v = (v ^ 0x5A) & 0xFF; | |
| 170 | outb[i] = (byte)v; | |
| 171 | } | |
| 172 | return outb; | |
| 173 | } | |
| 174 | ||
| 175 | [MethodImpl(MethodImplOptions.NoInlining)] | |
| 176 | private byte[] DumbMixC(byte[] input, byte[] nonce) | |
| 177 | { | |
| 178 | byte[] outb = new byte[input.Length]; | |
| 179 | for (int i = 0; i < input.Length; i++) | |
| 180 | { | |
| 181 | int n = nonce[i % nonce.Length]; | |
| 182 | int v = input[i]; | |
| 183 | v = ((v + n) ^ (n >> (i % 4))) & 0xFF; | |
| 184 | outb[i] = (byte)v; | |
| 185 | } | |
| 186 | return outb; | |
| 187 | } | |
| 188 | ||
| 189 | private byte[] GetSecretBytes() | |
| 190 | { | |
| 191 | byte[] k = new byte[obfPartA.Length + obfPartB.Length]; | |
| 192 | for (int i = 0; i < obfPartA.Length; i++) k[i] = (byte)(obfPartA[i] ^ Mask); | |
| 193 | for (int i = 0; i < obfPartB.Length; i++) k[i + obfPartA.Length] = (byte)(obfPartB[i] ^ Mask); | |
| 194 | ||
| 195 | if (!EnableHardening || !EnableDummyMix) | |
| 196 | { | |
| 197 | byte[] simple = new byte[k.Length]; | |
| 198 | Buffer.BlockCopy(k, 0, simple, 0, k.Length); | |
| 199 | Array.Clear(k, 0, k.Length); | |
| 200 | return simple; | |
| 201 | } | |
| 202 | ||
| 203 | byte[] s1 = DumbMixA(k); | |
| 204 | ||
| 205 | // 시간 의존 제거, 디바이스 기반 결정적 시드 | |
| 206 | int deviceSeed = Application.identifier.GetHashCode() ^ SystemInfo.deviceUniqueIdentifier.GetHashCode(); | |
| 207 | ||
| 208 | byte[] s2 = DumbMixB(s1, deviceSeed); | |
| 209 | byte[] s3 = DumbMixC(s2, sessionNonce); | |
| 210 | for (int r = 0; r < 2; r++) | |
| 211 | { | |
| 212 | byte[] t = DumbMixA(s3); | |
| 213 | byte[] u = DumbMixB(t, deviceSeed ^ r); | |
| 214 | for (int i = 0; i < k.Length; i++) s3[i] = (byte)(s3[i] ^ u[i % u.Length]); | |
| 215 | Array.Clear(t, 0, t.Length); | |
| 216 | Array.Clear(u, 0, u.Length); | |
| 217 | } | |
| 218 | Array.Clear(s1, 0, s1.Length); | |
| 219 | Array.Clear(s2, 0, s2.Length); | |
| 220 | ||
| 221 | byte[] result = new byte[k.Length]; | |
| 222 | for (int i = 0; i < k.Length; i++) result[i] = (byte)(k[i] ^ s3[i % s3.Length]); | |
| 223 | Array.Clear(k, 0, k.Length); | |
| 224 | Array.Clear(s3, 0, s3.Length); | |
| 225 | return result; | |
| 226 | } | |
| 227 | ||
| 228 | private (byte[] aesKey, byte[] hmacKey) DeriveKeysRotating(byte[] salt, byte[] rotationNonce) | |
| 229 | { | |
| 230 | byte[] secret = GetSecretBytes(); | |
| 231 | string secretStr = EnableHardening ? Convert.ToBase64String(secret) + Application.identifier : Encoding.UTF8.GetString(secret) + Application.identifier; | |
| 232 | Array.Clear(secret, 0, secret.Length); | |
| 233 | ||
| 234 | using (var kdf = new Rfc2898DeriveBytes(secretStr, salt, Pbkdf2Iterations, HashAlgorithmName.SHA256)) | |
| 235 | { | |
| 236 | byte[] km = kdf.GetBytes(KeyMaterialLen); | |
| 237 | if (EnableHardening && EnableRotation && rotationNonce != null) | |
| 238 | { | |
| 239 | for (int i = 0; i < km.Length; i++) km[i] ^= rotationNonce[i % rotationNonce.Length]; | |
| 240 | } | |
| 241 | byte[] aesKey = new byte[AesKeyLen]; | |
| 242 | byte[] hmacKey = new byte[HmacKeyLen]; | |
| 243 | Buffer.BlockCopy(km, 0, aesKey, 0, AesKeyLen); | |
| 244 | Buffer.BlockCopy(km, AesKeyLen, hmacKey, 0, HmacKeyLen); | |
| 245 | Array.Clear(km, 0, km.Length); | |
| 246 | return (aesKey, hmacKey); | |
| 247 | } | |
| 248 | } | |
| 249 | ||
| 250 | public void Save<T>(T data) | |
| 251 | { | |
| 252 | if (EnableHardening && (EnableAntiDebugChecks || EnableRootDetection) && IsTamperedOrDebug()) return; | |
| 253 | try | |
| 254 | { | |
| 255 | string json = JsonUtility.ToJson(data); | |
| 256 | byte[] salt = GenerateRandomBytes(SaltLenDefault); | |
| 257 | byte[] rotation = (EnableHardening && EnableRotation) ? GenerateRandomBytes(16) : new byte[16]; | |
| 258 | var keys = DeriveKeysRotating(salt, (EnableRotation ? rotation : null)); | |
| 259 | byte[] aesKey = keys.aesKey; | |
| 260 | byte[] hmacKey = keys.hmacKey; | |
| 261 | byte[] plain = Encoding.UTF8.GetBytes(json); | |
| 262 | byte[] iv; | |
| 263 | byte[] cipher; | |
| 264 | using (Aes aes = Aes.Create()) | |
| 265 | { | |
| 266 | aes.KeySize = 256; | |
| 267 | aes.Mode = CipherMode.CBC; | |
| 268 | aes.Padding = PaddingMode.PKCS7; | |
| 269 | aes.Key = aesKey; | |
| 270 | aes.GenerateIV(); | |
| 271 | iv = aes.IV; | |
| 272 | using (var enc = aes.CreateEncryptor(aes.Key, iv)) | |
| 273 | cipher = enc.TransformFinalBlock(plain, 0, plain.Length); | |
| 274 | } | |
| 275 | byte[] ivCipher = new byte[iv.Length + cipher.Length]; | |
| 276 | Buffer.BlockCopy(iv, 0, ivCipher, 0, iv.Length); | |
| 277 | Buffer.BlockCopy(cipher, 0, ivCipher, iv.Length, cipher.Length); | |
| 278 | byte[] hmac; | |
| 279 | using (var h = new HMACSHA256(hmacKey)) hmac = h.ComputeHash(ivCipher); | |
| 280 | using (MemoryStream ms = new MemoryStream()) | |
| 281 | { | |
| 282 | ms.Write(Encoding.ASCII.GetBytes(Magic), 0, 4); | |
| 283 | ms.WriteByte(Version); | |
| 284 | ms.WriteByte((byte)salt.Length); | |
| 285 | ms.Write(salt, 0, salt.Length); | |
| 286 | ms.WriteByte(EnableRotation ? (byte)rotation.Length : (byte)0); | |
| 287 | if (EnableRotation) ms.Write(rotation, 0, rotation.Length); | |
| 288 | ms.Write(iv, 0, iv.Length); | |
| 289 | byte[] cipherLenBytes = BitConverter.GetBytes((Int32)cipher.Length); | |
| 290 | if (!BitConverter.IsLittleEndian) Array.Reverse(cipherLenBytes); | |
| 291 | ms.Write(cipherLenBytes, 0, 4); | |
| 292 | ms.Write(cipher, 0, cipher.Length); | |
| 293 | ms.Write(hmac, 0, hmac.Length); | |
| 294 | string outBlob = Convert.ToBase64String(ms.ToArray()); | |
| 295 | File.WriteAllText(filePath, outBlob); | |
| 296 | } | |
| 297 | ClearBytes(aesKey); | |
| 298 | ClearBytes(hmacKey); | |
| 299 | ClearBytes(plain); | |
| 300 | ClearBytes(iv); | |
| 301 | ClearBytes(cipher); | |
| 302 | ClearBytes(ivCipher); | |
| 303 | ClearBytes(hmac); | |
| 304 | ClearBytes(salt); | |
| 305 | ClearBytes(rotation); | |
| 306 | } | |
| 307 | catch { } | |
| 308 | } | |
| 309 | ||
| 310 | public T Load<T>() | |
| 311 | { | |
| 312 | if (EnableHardening && (EnableAntiDebugChecks || EnableRootDetection) && IsTamperedOrDebug()) return default; | |
| 313 | if (!File.Exists(filePath)) return default; | |
| 314 | string blob = File.ReadAllText(filePath); | |
| 315 | byte[] total; | |
| 316 | try { total = Convert.FromBase64String(blob); } catch { return default; } | |
| 317 | try | |
| 318 | { | |
| 319 | using (MemoryStream ms = new MemoryStream(total)) | |
| 320 | using (BinaryReader br = new BinaryReader(ms)) | |
| 321 | { | |
| 322 | byte[] magic = br.ReadBytes(4); | |
| 323 | if (Encoding.ASCII.GetString(magic) != Magic) return default; | |
| 324 | byte ver = br.ReadByte(); | |
| 325 | if (ver != Version) return default; | |
| 326 | int saltLen = br.ReadByte(); | |
| 327 | if (saltLen <= 0 || saltLen > 64) return default; | |
| 328 | byte[] salt = br.ReadBytes(saltLen); | |
| 329 | int rotationLen = br.ReadByte(); | |
| 330 | byte[] rotation = rotationLen > 0 ? br.ReadBytes(rotationLen) : new byte[16]; | |
| 331 | byte[] iv = br.ReadBytes(IvLen); | |
| 332 | int cipherLen = br.ReadInt32(); | |
| 333 | if (!BitConverter.IsLittleEndian) cipherLen = System.Net.IPAddress.NetworkToHostOrder(cipherLen); | |
| 334 | if (cipherLen <= 0 || cipherLen > total.Length) return default; | |
| 335 | byte[] cipher = br.ReadBytes(cipherLen); | |
| 336 | byte[] hmac = br.ReadBytes(HmacLen); | |
| 337 | byte[] ivCipher = new byte[iv.Length + cipher.Length]; | |
| 338 | Buffer.BlockCopy(iv, 0, ivCipher, 0, iv.Length); | |
| 339 | Buffer.BlockCopy(cipher, 0, ivCipher, iv.Length, cipher.Length); | |
| 340 | var keys = DeriveKeysRotating(salt, (EnableRotation ? rotation : null)); | |
| 341 | byte[] aesKey = keys.aesKey; | |
| 342 | byte[] hmacKey = keys.hmacKey; | |
| 343 | byte[] expected; | |
| 344 | using (var h = new HMACSHA256(hmacKey)) expected = h.ComputeHash(ivCipher); | |
| 345 | bool ok = CryptographicOperations.FixedTimeEquals(expected, hmac); | |
| 346 | if (!ok) | |
| 347 | { | |
| 348 | ClearBytes(aesKey); | |
| 349 | ClearBytes(hmacKey); | |
| 350 | ClearBytes(expected); | |
| 351 | return default; | |
| 352 | } | |
| 353 | byte[] plain; | |
| 354 | using (Aes aes = Aes.Create()) | |
| 355 | { | |
| 356 | aes.KeySize = 256; | |
| 357 | aes.Mode = CipherMode.CBC; | |
| 358 | aes.Padding = PaddingMode.PKCS7; | |
| 359 | aes.Key = aesKey; | |
| 360 | aes.IV = iv; | |
| 361 | using (var dec = aes.CreateDecryptor(aes.Key, aes.IV)) | |
| 362 | plain = dec.TransformFinalBlock(cipher, 0, cipher.Length); | |
| 363 | } | |
| 364 | string json = Encoding.UTF8.GetString(plain); | |
| 365 | ClearBytes(aesKey); | |
| 366 | ClearBytes(hmacKey); | |
| 367 | ClearBytes(expected); | |
| 368 | ClearBytes(plain); | |
| 369 | ClearBytes(iv); | |
| 370 | ClearBytes(cipher); | |
| 371 | ClearBytes(salt); | |
| 372 | ClearBytes(rotation); | |
| 373 | return JsonUtility.FromJson<T>(json); | |
| 374 | } | |
| 375 | } | |
| 376 | catch { return default; } | |
| 377 | } | |
| 378 | ||
| 379 | private static byte[] GenerateRandomBytes(int len) | |
| 380 | { | |
| 381 | byte[] b = new byte[len]; | |
| 382 | using (var rng = RandomNumberGenerator.Create()) rng.GetBytes(b); | |
| 383 | return b; | |
| 384 | } | |
| 385 | ||
| 386 | private static void ClearBytes(byte[] b) | |
| 387 | { | |
| 388 | if (b == null) return; | |
| 389 | Array.Clear(b, 0, b.Length); | |
| 390 | } | |
| 391 | ||
| 392 | private string ResolveFileName(string baseName) | |
| 393 | { | |
| 394 | if (!EnableHardening || !EnableFileNameObfuscation) return baseName + ".dat"; | |
| 395 | byte[] nameBytes = Encoding.UTF8.GetBytes(baseName + Application.identifier); | |
| 396 | using (var sha = SHA256.Create()) | |
| 397 | { | |
| 398 | byte[] hash = sha.ComputeHash(nameBytes); | |
| 399 | string b64 = Convert.ToBase64String(hash); | |
| 400 | string safe = b64.Replace('+', '-').Replace('/', '_').Replace('=', 'x'); | |
| 401 | ClearBytes(hash); | |
| 402 | return safe.Substring(0, Math.Min(28, safe.Length)) + ".dat"; | |
| 403 | } | |
| 404 | } | |
| 405 | ||
| 406 | private bool IsTamperedOrDebug() | |
| 407 | { | |
| 408 | if (!EnableHardening) return false; | |
| 409 | try | |
| 410 | { | |
| 411 | if (EnableAntiDebugChecks) | |
| 412 | { | |
| 413 | if (System.Diagnostics.Debugger.IsAttached) return true; | |
| 414 | if (System.Diagnostics.Process.GetCurrentProcess().ProcessName.ToLower().Contains("debug")) return true; | |
| 415 | } | |
| 416 | } | |
| 417 | catch { } | |
| 418 | try | |
| 419 | { | |
| 420 | if (EnableRootDetection) | |
| 421 | { | |
| 422 | string[] suspectPaths = new string[] { "/system/bin/su", "/system/xbin/su", "/sbin/su" }; | |
| 423 | foreach (var p in suspectPaths) if (File.Exists(p)) return true; | |
| 424 | } | |
| 425 | } | |
| 426 | catch { } | |
| 427 | return false; | |
| 428 | } | |
| 429 | } | |
| 430 | }}} | |
| 58 | 431 | === 본 코드 === |
| 59 | 432 | {{{#!syntax csharp |
| 60 | 433 | using System; |
| ... | ... |