image-lib.ts 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. // box-nestjs-monorepo/libs/common/src/utils/image-lib.ts
  2. // You can extend this array with more headers if needed.
  3. export const ENCRYPTED_HEADERS: Uint8Array[] = [
  4. // Header from the Dart code: [0x88, 0xA8, 0x30, 0xCB, 0x10, 0x76]
  5. new Uint8Array([0x88, 0xa8, 0x30, 0xcb, 0x10, 0x76]),
  6. // Example: add more if their system uses multiple variants.
  7. // new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC]),
  8. ];
  9. /**
  10. * Pick a header index:
  11. * - if headerIndex is provided, use that
  12. * - otherwise pick one randomly from ENCRYPTED_HEADERS
  13. */
  14. function pickHeaderIndex(headerIndex?: number): number {
  15. if (
  16. typeof headerIndex === 'number' &&
  17. headerIndex >= 0 &&
  18. headerIndex < ENCRYPTED_HEADERS.length
  19. ) {
  20. return headerIndex;
  21. }
  22. if (ENCRYPTED_HEADERS.length === 0) {
  23. throw new Error(
  24. 'ENCRYPTED_HEADERS is empty; configure at least one header.',
  25. );
  26. }
  27. return Math.floor(Math.random() * ENCRYPTED_HEADERS.length);
  28. }
  29. /**
  30. * Encrypt image bytes by prefixing a chosen header.
  31. *
  32. * This is *obfuscation*, not real cryptography.
  33. *
  34. * @param imageBytes - original image content as Uint8Array
  35. * @param headerIndex - optional index into ENCRYPTED_HEADERS; if omitted, random header is used
  36. */
  37. export function encryptImageWithHeader(
  38. imageBytes: Uint8Array,
  39. headerIndex?: number,
  40. ): Uint8Array {
  41. if (!imageBytes || imageBytes.length === 0) {
  42. return imageBytes;
  43. }
  44. const idx = pickHeaderIndex(headerIndex);
  45. const header = ENCRYPTED_HEADERS[idx];
  46. // Allocate new array: [HEADER][IMAGE]
  47. const result = new Uint8Array(header.length + imageBytes.length);
  48. // Copy header
  49. result.set(header, 0);
  50. // Copy original image bytes after header
  51. result.set(imageBytes, header.length);
  52. return result;
  53. }
  54. /**
  55. * Try to detect which header (if any) the given bytes start with.
  56. *
  57. * @returns { headerIndex: number; header: Uint8Array } if matched, otherwise null.
  58. */
  59. function detectHeaderPrefix(
  60. bytes: Uint8Array,
  61. ): { headerIndex: number; header: Uint8Array } | null {
  62. if (!bytes || bytes.length === 0) return null;
  63. for (let i = 0; i < ENCRYPTED_HEADERS.length; i++) {
  64. const header = ENCRYPTED_HEADERS[i];
  65. if (bytes.length < header.length) continue;
  66. let match = true;
  67. for (let j = 0; j < header.length; j++) {
  68. if (bytes[j] !== header[j]) {
  69. match = false;
  70. break;
  71. }
  72. }
  73. if (match) {
  74. return { headerIndex: i, header };
  75. }
  76. }
  77. return null;
  78. }
  79. /**
  80. * Decrypt image bytes by stripping a known header if present.
  81. *
  82. * If no known header is found, the original bytes are returned unchanged.
  83. *
  84. * @param encryptedBytes - stored bytes (possibly with header)
  85. */
  86. export function decryptImageWithHeader(encryptedBytes: Uint8Array): Uint8Array {
  87. if (!encryptedBytes || encryptedBytes.length === 0) {
  88. return encryptedBytes;
  89. }
  90. const detected = detectHeaderPrefix(encryptedBytes);
  91. if (!detected) {
  92. // Not "encrypted" with our header scheme → return as-is.
  93. return encryptedBytes;
  94. }
  95. const { header } = detected;
  96. const start = header.length;
  97. // Slice bytes after the header, preserving the original image content.
  98. return encryptedBytes.subarray(start);
  99. }