Vui Vẻ
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note No publishing access yet

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.

      Your account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

      Your team account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

      Explore these features while you wait
      Complete general settings
      Bookmark and like published notes
      Write a few more notes
      Complete general settings
      Write a few more notes
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Make a copy
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Make a copy Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note No publishing access yet

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.

    Your account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

    Your team account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

    Explore these features while you wait
    Complete general settings
    Bookmark and like published notes
    Write a few more notes
    Complete general settings
    Write a few more notes
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # AES - Advanced Encryption Standard https://hackmd.io/@L1ttl3NoPro/SyUIJ_Su-e Là kỹ thuật mã hóa khối, dữ liệu đưa vào được chia thành các block 128 bit tạo thành ma trận 4x4 ( mỗi ô 8 bit ) - gọi là state, sau đó kết hợp với key 128, 192, 256 bit ( ở đây ta nói về AES-128 nên sử dụng key 128 bit ) qua các bước Add Round Key, Sub Bytes, Shift Rows, Mix columns nhiều lần ( 10, 12, 14 tùy theo độ dài của key) tạo thành ciphertext. Lưu ý: Trong AES các bước thực hiện đều xử lí theo cột ( column major ), khi chuyển từ ma trận sang bytes thì xử lí theo hàng ( row major ). # Cấu trúc ![image](https://hackmd.io/_uploads/rkvBUAmb-l.png) ## KeyExpansion Là bước biến key 128 bit thành ma trận RoundKey 4x4 để cho bước AddRoundKey ở sau. ## AddRoundKey Là bước xor các bytes trong state với RoundKey ở vị trí tương ứng nhau tạo thành 1 ma trận state mới. ![image](https://hackmd.io/_uploads/rkhiORXbWg.png) ### Rounds Key ``` state = [ [206, 243, 61, 34], [171, 11, 93, 31], [16, 200, 91, 108], [150, 3, 194, 51], ] round_key = [ [173, 129, 68, 82], [223, 100, 38, 109], [32, 189, 53, 8], [253, 48, 187, 78], ] def add_round_key(s, k): return [[s[j][i] ^ k[j][i] for j in range(4)] for i in range(4)] def matrix2bytes(matrix): return bytes(matrix[b][a] for a in range (4) for b in range(4)).decode() print(matrix2bytes(add_round_key(state, round_key))) ``` ## SubBytes Ở bước này ta có một ma trận S_box( Substitution box ): ``` s_box = ( 0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76, 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0, 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15, 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75, 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84, 0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF, 0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8, 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, 0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73, 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB, 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, 0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08, 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A, 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, 0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF, 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16, ) ``` S_box được tạo theo công thức trong GF(2^8): ![image](https://hackmd.io/_uploads/Bk7usCX-Zg.png) Trong bước SubBytes, thay các bytes trong state thành các bytes trong S_box được quy định tức state[i][j] = s_box[ state[i][j] ]. Khi giải mã, ta sử dụng bảng inverse S_box để giải. ### Confusion through Substitution ```= s_box = ( 0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76, 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0, 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15, 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75, 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84, 0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF, 0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8, 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, 0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73, 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB, 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, 0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08, 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A, 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, 0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF, 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16, ) inv_s_box = ( 0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB, 0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB, 0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E, 0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25, 0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92, 0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84, 0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06, 0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B, 0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73, 0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E, 0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B, 0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4, 0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F, 0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF, 0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61, 0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D, ) state = [ [251, 64, 182, 81], [146, 168, 33, 80], [199, 159, 195, 24], [64, 80, 182, 255], ] def matrix2bytes(matrix): return bytes(sum(matrix,[])).decode() def sub_bytes(s, sbox=s_box): for i in range(4): for j in range(4): s[i][j] = sbox[s[i][j]] return s print(matrix2bytes(sub_bytes(state, sbox=inv_s_box))) ``` ## ShiftRows & MixColumns ### ShiftRows Là bước dịch xoay vòng state qua trái, hàng đầu giữ nguyên, hàng 2 dịch 1 lần, hàng 3 dịch 2 lần, hàng 4 dịch 3 lần. ![image](https://hackmd.io/_uploads/r1SrSJEbWl.png) ### MixColumns Bước này xem các cột của state là đa thức và nhân với đa thức c(x) = 3 * $x^3$ + $x^2$ + $x$ + 2 mod( $x^4$ + 1) ![image](https://hackmd.io/_uploads/BJkHL1E--l.png) ### Diffusion through Permutation ```= def shift_rows(s): s[0][1], s[1][1], s[2][1], s[3][1] = s[1][1], s[2][1], s[3][1], s[0][1] s[0][2], s[1][2], s[2][2], s[3][2] = s[2][2], s[3][2], s[0][2], s[1][2] s[0][3], s[1][3], s[2][3], s[3][3] = s[3][3], s[0][3], s[1][3], s[2][3] ''' 0 1 2 3 0 5 A F 4 5 6 7 4 9 E 3 8 9 A B 8 D 2 7 C D E F C 1 6 B ''' def inv_shift_rows(s): s[0][1], s[1][1], s[2][1], s[3][1] = s[3][1], s[0][1], s[1][1], s[2][1] s[0][2], s[1][2], s[2][2], s[3][2] = s[2][2], s[3][2], s[0][2], s[1][2] s[0][3], s[1][3], s[2][3], s[3][3] = s[1][3], s[2][3], s[3][3], s[0][3] # learned from http://cs.ucsb.edu/~koc/cs178/projects/JT/aes.c xtime = lambda a: (((a << 1) ^ 0x1B) & 0xFF) if (a & 0x80) else (a << 1) def mix_single_column(a): # see Sec 4.1.2 in The Design of Rijndael t = a[0] ^ a[1] ^ a[2] ^ a[3] u = a[0] a[0] ^= t ^ xtime(a[0] ^ a[1]) a[1] ^= t ^ xtime(a[1] ^ a[2]) a[2] ^= t ^ xtime(a[2] ^ a[3]) a[3] ^= t ^ xtime(a[3] ^ u) def mix_columns(s): for i in range(4): mix_single_column(s[i]) def inv_mix_columns(s): # see Sec 4.1.3 in The Design of Rijndael for i in range(4): u = xtime(xtime(s[i][0] ^ s[i][2])) v = xtime(xtime(s[i][1] ^ s[i][3])) s[i][0] ^= u s[i][1] ^= v s[i][2] ^= u s[i][3] ^= v mix_columns(s) def matrix2bytes(matrix): return bytes(sum(matrix, [])) state = [ [108, 106, 71, 86], [96, 62, 38, 72], [42, 184, 92, 209], [94, 79, 8, 54], ] inv_mix_columns(state) inv_shift_rows(state) flag = matrix2bytes(state).decode() print(flag) ``` ## Bringing It All Together Khi biết được các bước encrypt của AES thì decrypt chỉ cần thực hiện theo hướng ngược lại, gộp các bước lại ta được 1 bài decrypt hoàn chỉnh: ```= N_ROUNDS = 10 key = b'\xc3,\\\xa6\xb5\x80^\x0c\xdb\x8d\xa5z*\xb6\xfe\\' ciphertext = b'\xd1O\x14j\xa4+O\xb6\xa1\xc4\x08B)\x8f\x12\xdd' s_box = ( 0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76, 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0, 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15, 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75, 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84, 0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF, 0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8, 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, 0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73, 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB, 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, 0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08, 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A, 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, 0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF, 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16, ) inv_s_box = ( 0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB, 0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB, 0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E, 0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25, 0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92, 0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84, 0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06, 0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B, 0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73, 0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E, 0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B, 0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4, 0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F, 0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF, 0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61, 0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D, ) def inv_shift_rows(s): s[0][1], s[1][1], s[2][1], s[3][1] = s[3][1], s[0][1], s[1][1], s[2][1] s[0][2], s[1][2], s[2][2], s[3][2] = s[2][2], s[3][2], s[0][2], s[1][2] s[0][3], s[1][3], s[2][3], s[3][3] = s[1][3], s[2][3], s[3][3], s[0][3] xtime = lambda a: (((a << 1) ^ 0x1B) & 0xFF) if (a & 0x80) else (a << 1) def mix_single_column(a): # see Sec 4.1.2 in The Design of Rijndael t = a[0] ^ a[1] ^ a[2] ^ a[3] u = a[0] a[0] ^= t ^ xtime(a[0] ^ a[1]) a[1] ^= t ^ xtime(a[1] ^ a[2]) a[2] ^= t ^ xtime(a[2] ^ a[3]) a[3] ^= t ^ xtime(a[3] ^ u) def mix_columns(s): for i in range(4): mix_single_column(s[i]) def inv_mix_columns(s): # see Sec 4.1.3 in The Design of Rijndael for i in range(4): u = xtime(xtime(s[i][0] ^ s[i][2])) v = xtime(xtime(s[i][1] ^ s[i][3])) s[i][0] ^= u s[i][1] ^= v s[i][2] ^= u s[i][3] ^= v mix_columns(s) def sub_bytes(s, sbox=s_box): for c in range(4): for r in range(4): s[c][r] = sbox[s[c][r]] def add_round_key(s, round_key, idx): # round_key is same shape as s: [col0, col1, col2, col3] for c in range(4): for r in range(4): s[c][r] ^= round_key[idx][c][r] return s def bytes2matrix(text): return [list(text[i:i+4]) for i in range(0, len(text), 4)] def matrix2bytes(matrix): out = [] for r in matrix: for c in r: out.append(c.to_bytes(2,byteorder='little').decode()) return ''.join(out) def expand_key(master_key): """ Expands and returns a list of key matrices for the given master_key. """ # Round constants https://en.wikipedia.org/wiki/AES_key_schedule#Round_constants r_con = ( 0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36, 0x6C, 0xD8, 0xAB, 0x4D, 0x9A, 0x2F, 0x5E, 0xBC, 0x63, 0xC6, 0x97, 0x35, 0x6A, 0xD4, 0xB3, 0x7D, 0xFA, 0xEF, 0xC5, 0x91, 0x39, ) # Initialize round keys with raw key material. key_columns = bytes2matrix(master_key) iteration_size = len(master_key) // 4 # Each iteration has exactly as many columns as the key material. i = 1 while len(key_columns) < (N_ROUNDS + 1) * 4: # Copy previous word. word = list(key_columns[-1]) # Perform schedule_core once every "row". if len(key_columns) % iteration_size == 0: # Circular shift. word.append(word.pop(0)) # Map to S-BOX. word = [s_box[b] for b in word] # XOR with first byte of R-CON, since the others bytes of R-CON are 0. word[0] ^= r_con[i] i += 1 elif len(master_key) == 32 and len(key_columns) % iteration_size == 4: # Run word through S-box in the fourth iteration when using a # 256-bit key. word = [s_box[b] for b in word] # XOR with equivalent word from previous iteration. word = bytes(i^j for i, j in zip(word, key_columns[-iteration_size])) key_columns.append(word) # Group key words in 4x4 byte matrices. return [key_columns[4*i : 4*(i+1)] for i in range(len(key_columns) // 4)] def decrypt(key, ciphertext): round_keys = expand_key(key) # Remember to start from the last round key and work backwards through them when decrypting # Convert ciphertext to state matrix state = bytes2matrix(ciphertext) # Initial add round key step add_round_key(state, round_keys, 10) for i in range(N_ROUNDS - 1, 0, -1): inv_shift_rows(state) sub_bytes(state, sbox = inv_s_box) add_round_key(state, round_keys, i) inv_mix_columns(state) # Run final round (skips the InvMixColumns step) inv_shift_rows(state) sub_bytes(state, sbox = inv_s_box) add_round_key(state, round_keys, 0) # Convert state matrix to plaintext plaintext = matrix2bytes(state) return plaintext print(decrypt(key, ciphertext)) ``` # Cryptohack ## Modes of Operation Starter ### Đề : ```= from Crypto.Cipher import AES KEY = ? FLAG = ? @chal.route('/block_cipher_starter/decrypt/<ciphertext>/') def decrypt(ciphertext): ciphertext = bytes.fromhex(ciphertext) cipher = AES.new(KEY, AES.MODE_ECB) try: decrypted = cipher.decrypt(ciphertext) except ValueError as e: return {"error": str(e)} return {"plaintext": decrypted.hex()} @chal.route('/block_cipher_starter/encrypt_flag/') def encrypt_flag(): cipher = AES.new(KEY, AES.MODE_ECB) encrypted = cipher.encrypt(FLAG.encode()) return {"ciphertext": encrypted.hex()} ``` ![image](https://hackmd.io/_uploads/Hyk5b7N-Wx.png) Bài này cho ta biết về Mode ECB trong AES: plaintext được chia thành các block và đưa vào block encrypt cùng với key để ra được block ciphertext. Vì ECB là mã hóa block đơn thuẩn, không có sự liên kết giữa các block với nhau nên nếu cùng 1 block, cùng 1 plaintext và key thì ciphertext sẽ giống nhau. Đây là nhược điểm lớn nhất của ECB. Cách giải bài này chỉ cần lấy ciphertext và đưa lại vào ô decrypt và unhex là được > crypto{bl0ck_c1ph3r5_4r3_f457_!} ## Passwords as Keys ### Đề : ```= from Crypto.Cipher import AES import hashlib import random # /usr/share/dict/words from # https://gist.githubusercontent.com/wchargin/8927565/raw/d9783627c731268fb2935a731a618aa8e95cf465/words with open("/usr/share/dict/words") as f: words = [w.strip() for w in f.readlines()] keyword = random.choice(words) KEY = hashlib.md5(keyword.encode()).digest() FLAG = ? @chal.route('/passwords_as_keys/decrypt/<ciphertext>/<password_hash>/') def decrypt(ciphertext, password_hash): ciphertext = bytes.fromhex(ciphertext) key = bytes.fromhex(password_hash) cipher = AES.new(key, AES.MODE_ECB) try: decrypted = cipher.decrypt(ciphertext) except ValueError as e: return {"error": str(e)} return {"plaintext": decrypted.hex()} @chal.route('/passwords_as_keys/encrypt_flag/') def encrypt_flag(): cipher = AES.new(KEY, AES.MODE_ECB) encrypted = cipher.encrypt(FLAG.encode()) return {"ciphertext": encrypted.hex()} ``` Chạy tất cả các word trong file words.txt, chuyển thành key và decrypt, nếu như trong decrypt có b"crypto{" thì key đó đúng, và đó cũng là flag. ### Script giải : ```= import requests import hashlib from Crypto.Cipher import AES BASE = "https://aes.cryptohack.org/passwords_as_keys" def encrypt(): url = f"{BASE}/encrypt_flag/" r = requests.get(url) data = r.json() return data["ciphertext"] def decrypt( ct, key ): url = f"{BASE}/decrypt/{ct}/{key}/" r = requests.get(url) data = r.json() return data["plaintext"] ct = encrypt() with open("words.txt", "r", encoding="utf-8") as f: for line in f: line = line.strip() if line: key = hashlib.md5(line.encode()).hexdigest() pt = decrypt( ct , key ) pt = bytes.fromhex( pt ) print(line) if( b"crypto{" in pt ): print( pt ) exit(0) ``` > crypto{k3y5__r__n07__p455w0rdz?} ## ECB Oracle ### Đề : ```= @chal.route('/ecb_oracle/encrypt/<plaintext>/') def encrypt(plaintext): plaintext = bytes.fromhex(plaintext) padded = pad(plaintext + FLAG.encode(), 16) cipher = AES.new(KEY, AES.MODE_ECB) try: encrypted = cipher.encrypt(padded) except ValueError as e: return {"error": str(e)} return {"ciphertext": encrypted.hex()} ``` Với tính chất mã hóa độc lập của ECB, ta có thể sử dụng nó để tìm ra flag bằng cách brute từng kí tự của flag. Khi nhập vào 15 kí tự "a" thì trong hàm encrypt sẽ thêm flag vào và pad thêm đủ độ dài là bội của 16 -> padded = 15 * "a" + flag + padding Vậy thì block đầu của plaintext là 15 * "a" + bytes đầu của flag ( ở đây ta biết byte đầu của flag là b"c" -> aaaaaaaaaaaaaaac ) ### VD : ( Vd bằng bytes thay vì hex cho dễ hình dung) #### Giả sử flag có dạng crypto{FLAG}: pt = b"" Tìm byte đầu: plaintext = b"aaaaaaaaaaaaaaa" padded = b"aaaaaaaaaaaaaaa" + b"crypto{FLAG}" + b"0" * 5 block đầu = b"aaaaaaaaaaaaaaac" server sẽ tự đưa block đầu vào và trả ciphertex, sau đó ta chỉ việc brute tìm ra byte thỏa điều kiện 2 ciphertext bằng nhau. guess = b"aaaaaaaaaaaaaaa" + pt + byte dự đoán Cứ lặp lại các bước cho đến khi tìm ra flag. ### Script giải : ```= import requests import hashlib from Crypto.Cipher import AES BASE = "https://aes.cryptohack.org/ecb_oracle" def encrypt( pt ): pt = pt.hex() url = f"{BASE}/encrypt/{ pt }/" r = requests.get(url) data = r.json() return data["ciphertext"] flag = b"" block = 0 idx = 15 letters = b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_{}" while ( b"}" not in flag ): if( (len(flag) + 1) % 16 != 0 ): send = b"a" * ( idx - len(flag) % 16 ) else: send = b"a" * 16 ct = encrypt( send ) block = len(flag) // 16 for i in letters : if( (len(flag) + 1) % 16 != 0 ): test = b"a" * ( idx - len(flag) % 16 ) + flag + bytes([ i ]) test_enc = encrypt( test ) if( test_enc[block * 32 : (block + 1) * 32 ] == ct[block * 32 : (block + 1 ) * 32 ] ): flag += bytes([ i ]) break else: test = b"a" * 16 + flag + bytes([ i ]) test_enc = encrypt( test ) if( test_enc[(block + 1) * 32 : (block + 2) * 32 ] == ct[(block + 1) * 32 : (block + 2 ) * 32 ] ): flag += bytes([ i ]) break print( flag ) ``` > crypto{p3n6u1n5_h473_3cb} ## ECB CBC WTF ### Đề : ```= from Crypto.Cipher import AES KEY = ? FLAG = ? @chal.route('/ecbcbcwtf/decrypt/<ciphertext>/') def decrypt(ciphertext): ciphertext = bytes.fromhex(ciphertext) cipher = AES.new(KEY, AES.MODE_ECB) try: decrypted = cipher.decrypt(ciphertext) except ValueError as e: return {"error": str(e)} return {"plaintext": decrypted.hex()} @chal.route('/ecbcbcwtf/encrypt_flag/') def encrypt_flag(): iv = os.urandom(16) cipher = AES.new(KEY, AES.MODE_CBC, iv) encrypted = cipher.encrypt(FLAG.encode()) ciphertext = iv.hex() + encrypted.hex() return {"ciphertext": ciphertext} ``` Bài này encrypt bằng Mode CBC nhưng decrypt bằng Mode ECB. Về CBC Mode: ![image](https://hackmd.io/_uploads/HkGYS7r--x.png) ![image](https://hackmd.io/_uploads/S1SDMmr--g.png) CBC là dạng block cipher an toàn hơn ECB vì có kết hợp thêm IV vào, và các IV thay đổi sau mỗi encrypted block, ở block đầu tiên IV giữ nguyên, block kế tiếp sử dụng ciphertext để làm IV, việc này khiến cho CBC có tính chất giống với stream cipher. Khi lấy ciphertext ta được 1 đoạn 48 bytes bao gồm 16 bytes IV và 32 bytes ciphertext. Ta decrypt bằng cách đưa từng block ciphertext vào và xor với IV. ### Script giải : ```= import requests from Crypto.Util.number import long_to_bytes from Crypto.Cipher import AES BASE = "https://aes.cryptohack.org/ecbcbcwtf" def encrypt(): url = f"{BASE}/encrypt_flag/" r = requests.get(url) data = r.json() return data["ciphertext"] def decrypt( ct ): url = f"{BASE}/decrypt/{ct}/" r = requests.get(url) data = r.json() return data["plaintext"] ct = encrypt() iv = ct[:32] ct = ct[32:] pt = b"" blocks = len( ct ) // 32 for i in range ( blocks ): send = ct[ i * 32 : ( i + 1 ) * 32 ] data = decrypt( send ) text = int( data, 16 ) ^ int( iv, 16 ) pt += long_to_bytes( text ) iv = send print( pt.decode() ) ``` > crypto{3cb_5uck5_4v01d_17_!!!!!} ## Flipping Cookie ### Đề : ```= from Crypto.Cipher import AES import os from Crypto.Util.Padding import pad, unpad from datetime import datetime, timedelta KEY = ? FLAG = ? @chal.route('/flipping_cookie/check_admin/<cookie>/<iv>/') def check_admin(cookie, iv): cookie = bytes.fromhex(cookie) iv = bytes.fromhex(iv) try: cipher = AES.new(KEY, AES.MODE_CBC, iv) decrypted = cipher.decrypt(cookie) unpadded = unpad(decrypted, 16) except ValueError as e: return {"error": str(e)} if b"admin=True" in unpadded.split(b";"): return {"flag": FLAG} else: return {"error": "Only admin can read the flag"} @chal.route('/flipping_cookie/get_cookie/') def get_cookie(): expires_at = (datetime.today() + timedelta(days=1)).strftime("%s") cookie = f"admin=False;expiry={expires_at}".encode() iv = os.urandom(16) padded = pad(cookie, 16) cipher = AES.new(KEY, AES.MODE_CBC, iv) encrypted = cipher.encrypt(padded) ciphertext = iv.hex() + encrypted.hex() return {"cookie": ciphertext} ``` Công thức encypt và decrypt là: **Encrypt:** Block đầu: $$ C_0 = E_k(P_0 \oplus IV) $$ Các block sau: $$ C_i = E_k(P_i \oplus C_{i-1}) \quad\text{với } i \ge 1 $$ **Decrypt:** Block đầu: $$ P_0 = D_k(C_0) \oplus IV $$ Các block sau: $$ P_i = D_k(C_i) \oplus C_{i-1} \quad\text{với } i \ge 1 $$ ### Script giải : ```= import requests from Crypto.Util.number import long_to_bytes, bytes_to_long from Crypto.Cipher import AES BASE = "https://aes.cryptohack.org/flipping_cookie" def check( cookie , iv ): url = f"{BASE}/check_admin/{cookie}/{iv}/" r = requests.get(url) data = r.json() return data["flag"] def get_cookie(): url = f"{BASE}/get_cookie/" r = requests.get(url) data = r.json() return data["cookie"] cookie = get_cookie() iv = cookie[:32] cookie = cookie[32:] iv = bytearray.fromhex(iv) iv[6] ^= ord("F") ^ ord("T") iv[7] ^= ord("a") ^ ord("r") iv[8] ^= ord("l") ^ ord("u") iv[9] ^= ord("s") ^ ord("e") iv[10] ^= ord("e") ^ ord(";") iv = iv.hex() pt = check( cookie , iv ) print( pt ) ``` > crypto{4u7h3n71c4710n_15_3553n714l} ## Symmetry ### Đề : ```= from Crypto.Cipher import AES KEY = ? FLAG = ? @chal.route('/symmetry/encrypt/<plaintext>/<iv>/') def encrypt(plaintext, iv): plaintext = bytes.fromhex(plaintext) iv = bytes.fromhex(iv) if len(iv) != 16: return {"error": "IV length must be 16"} cipher = AES.new(KEY, AES.MODE_OFB, iv) encrypted = cipher.encrypt(plaintext) ciphertext = encrypted.hex() return {"ciphertext": ciphertext} @chal.route('/symmetry/encrypt_flag/') def encrypt_flag(): iv = os.urandom(16) cipher = AES.new(KEY, AES.MODE_OFB, iv) encrypted = cipher.encrypt(FLAG.encode()) ciphertext = iv.hex() + encrypted.hex() return {"ciphertext": ciphertext} ``` Bài này được giới thiệu thêm về Mode OFB trong AES ![image](https://hackmd.io/_uploads/r1gtGBESWZl.png) ![image](https://hackmd.io/_uploads/SJdQHNrW-e.png) OFB là một block cipher có tính chất stream cipher. Công thức: Keystream tạo như sau: $$ K_0 = E_k(IV) $$ $$ K_i = E_k(K_{i-1}) \quad \text{với } i \ge 1 $$ Encrypt: $$ C_i = P_i \oplus K_i $$ Decrypt: $$ P_i = C_i \oplus K_i $$ ### Script giải : ```= import requests from Crypto.Util.number import long_to_bytes, bytes_to_long from Crypto.Cipher import AES BASE = "https://aes.cryptohack.org/symmetry" def encrypt( plaintext , iv ): url = f"{BASE}/encrypt/{plaintext}/{iv}/" r = requests.get(url) data = r.json() return data["ciphertext"] def encrypt_flag(): url = f"{BASE}/encrypt_flag/" r = requests.get(url) data = r.json() return data["ciphertext"] ct = encrypt_flag() iv = ct[:32] ct = ct[32:] pt = encrypt( ct , iv ) print( bytes.fromhex( pt ).decode() ) ``` > crypto{0fb_15_5ymm37r1c4l_!!!11!} ## Bean Counter ### Đề : ```= from Crypto.Cipher import AES KEY = ? class StepUpCounter(object): def __init__(self, step_up=False): self.value = os.urandom(16).hex() self.step = 1 self.stup = step_up def increment(self): if self.stup: self.newIV = hex(int(self.value, 16) + self.step) else: self.newIV = hex(int(self.value, 16) - self.stup) self.value = self.newIV[2:len(self.newIV)] return bytes.fromhex(self.value.zfill(32)) def __repr__(self): self.increment() return self.value @chal.route('/bean_counter/encrypt/') def encrypt(): cipher = AES.new(KEY, AES.MODE_ECB) ctr = StepUpCounter() out = [] with open("challenge_files/bean_flag.png", 'rb') as f: block = f.read(16) while block: keystream = cipher.encrypt(ctr.increment()) xored = [a^b for a, b in zip(block, keystream)] out.append(bytes(xored).hex()) block = f.read(16) return {"encrypted": ''.join(out)} ``` ![image](https://hackmd.io/_uploads/H1RvoEHbZg.png) ![image](https://hackmd.io/_uploads/SyrYiVr-We.png) Mode CTR là dạng block cipher độc lập, điều đặc biệt ở đây là thay vì sử dụng IV thì Mode CTR dùng 1 nonce và bộ đếm counter thay đổi. Mỗi block cipher được encrypt và decrypt theo công thức: Keystream: $$ K_i = E_k(\text{nonce} \,\|\, \text{counter}_i) $$ Encrypt: $$ C_i = P_i \oplus K_i $$ Decrypt: $$ P_i = C_i \oplus K_i $$ Đề này có 1 lỗ hổng là khi gọi class $StepUpCounter$, nó đặt step_up = 0 và stup cũng bằng 0. ![image](https://hackmd.io/_uploads/HJCRTDBbZe.png) Hàm increment dùng để tăng/ giảm bộ đếm nhưng vì stup = 0 nên newIV vẫn bằng IV cũ. Vậy là counter không đổi trong cả quá trình này => Cùng keystream ![image](https://hackmd.io/_uploads/HkT8RvrbWl.png) Vậy nên ta chỉ cần tìm ra keystream thì sẽ tìm ra plaintext. 16 bytes đầu của file png là b"\x89PNG\r\n\x1a\n\x00\x00\x00\x0dIHDR" và cũng là 16 bytes đầu của plaintext. Ta chỉ cần xor ngược plaintext với cipher text là tìm được keystream. ### Script giải : ```= import requests from Crypto.Util.number import long_to_bytes, bytes_to_long from Crypto.Cipher import AES BASE = "https://aes.cryptohack.org/bean_counter" def encrypt(): url = f"{BASE}/encrypt/" r = requests.get(url) data = r.json() return data["encrypted"] def xor_bytes(a, b): return bytes([x ^ y for x, y in zip(a, b)]) data = encrypt() ct = [bytes.fromhex( data[ i : i + 32 ] ) for i in range(0, len(data), 32) ] pt = b"" png_header = b"\x89PNG\r\n\x1a\n\x00\x00\x00\x0dIHDR" keystream = xor_bytes( png_header , ct[0]) for block in ct: pt += xor_bytes( keystream , block ) with open("flag.png" , "wb") as f: f.write( pt ) ``` > ![flag](https://hackmd.io/_uploads/Bk2zaDHZbe.png) ## Lazy CBC ### Đề : ```python= from Crypto.Cipher import AES KEY = ? FLAG = ? @chal.route('/lazy_cbc/encrypt/<plaintext>/') def encrypt(plaintext): plaintext = bytes.fromhex(plaintext) if len(plaintext) % 16 != 0: return {"error": "Data length must be multiple of 16"} cipher = AES.new(KEY, AES.MODE_CBC, KEY) encrypted = cipher.encrypt(plaintext) return {"ciphertext": encrypted.hex()} @chal.route('/lazy_cbc/get_flag/<key>/') def get_flag(key): key = bytes.fromhex(key) if key == KEY: return {"plaintext": FLAG.encode().hex()} else: return {"error": "invalid key"} @chal.route('/lazy_cbc/receive/<ciphertext>/') def receive(ciphertext): ciphertext = bytes.fromhex(ciphertext) if len(ciphertext) % 16 != 0: return {"error": "Data length must be multiple of 16"} cipher = AES.new(KEY, AES.MODE_CBC, KEY) decrypted = cipher.decrypt(ciphertext) try: decrypted.decode() # ensure plaintext is valid ascii except UnicodeDecodeError: return {"error": "Invalid plaintext: " + decrypted.hex()} return {"success": "Your message has been received"} ``` Đề bài yêu cầu tìm lại key và gửi để server xác nhận, đồng thời iv cũng là key, vậy trong mode CBC này key và iv là một. Tìm kiếm trên mạng em thấy blog về cách khôi phục key khi key và iv là một https://bernardoamc.com/ecb-iv-as-key/ Các bước thực hiện: - Tạo một plaintext có độ dài ít nhất 3 block và mã hóa thành ciphertext. - Thay đổi block thứ hai của ciphertext vừa tạo thành block giá trị 0. - Thay đổi block thứ ba của ciphertext thành block 1. - Giải mã đoạn ciphertext vừa đổi được thành đoạn invalid plaintext. - Thực hiện xor block đầu tiên và block cuối của invalid plaintext, kết quả là key cần tìm. **Giải thích:** Công thức mã hóa và giải mã của mode CBC là: Mã hóa: $$C_1=E_k(P_1 \oplus IV )$$ $$C_i=E_k(P_i \oplus C_{i-1} ) $$ Giải mã: $$P_1=IV \oplus D_k(C_1) $$ $$P_i= C_{i-1} \oplus D_k(C_i)$$ Sau khi gửi plaintext và nhận được ciphertext là C, thay đổi block thứ 2 và 3 ta được C có dạng: block1 + "0"*16 + block 1. Giải mã từng block : - $P_1=IV \oplus D_k(C_1)$ - $P_2=C_1 \oplus D_k(C_2)$ - $P_3=C_2 \oplus D_K(C_3)$ Khi xor $P_3$ với $P_1$ ta khử được $D_k(C_1)$ và $D_k(C_3)$ vì $C_1=C_3$, còn lại $IV \oplus C_2 = IV$ vì $C_2=$"0"*16. IV lại chính là key! ## Triple DES ### Đề: ```python= from Crypto.Cipher import DES3 from Crypto.Util.Padding import pad IV = os.urandom(8) FLAG = ? def xor(a, b): # xor 2 bytestrings, repeating the 2nd one if necessary return bytes(x ^ y for x,y in zip(a, b * (1 + len(a) // len(b)))) @chal.route('/triple_des/encrypt/<key>/<plaintext>/') def encrypt(key, plaintext): try: key = bytes.fromhex(key) plaintext = bytes.fromhex(plaintext) plaintext = xor(plaintext, IV) cipher = DES3.new(key, DES3.MODE_ECB) ciphertext = cipher.encrypt(plaintext) ciphertext = xor(ciphertext, IV) return {"ciphertext": ciphertext.hex()} except ValueError as e: return {"error": str(e)} @chal.route('/triple_des/encrypt_flag/<key>/') def encrypt_flag(key): return encrypt(key, pad(FLAG.encode(), 8).hex()) ``` ![image](https://hackmd.io/_uploads/HkrdJjtt-g.png) Triple DES là kĩ thuật mã hóa khối với 3 DES khác nhau ( 3 key khác nhau ). Công thức mã hóa là $$ C=E_{K_3}(D_{K_2}(E_{K_1}(P)))$$ Công thức giải mã ngược lại là $$P=D_{K_1}(E_{K_2}(D_{K_3}(C)))$$ 3DES có thể bị tấn công bằng Meet-in-the-middle( MITM ), và DES hay 3DES có Weak key, weak key là key làm cho quá trình giải mã và mã hóa trở thành một. Các weak key : ![image](https://hackmd.io/_uploads/ryKnZ6YYbx.png) ![image](https://hackmd.io/_uploads/r1w6-CKKbg.png) Tuy nhiên không phải mọi weak key đều dùng được, ở đây em đã thử và thấy key "0000000000000000FFFFFFFFFFFFFFFF" dùng được. Ta chỉ cần đưa key vào và lấy encrypted flag, sau đó encrypt tiếp với key và encrypted flag để thu được flag. https://en.wikipedia.org/wiki/Weak_key https://crypto.stackexchange.com/questions/12214/can-you-explain-weak-keys-for-des ## CTRIME ### Đề: ```python= from Crypto.Cipher import AES from Crypto.Util import Counter import zlib KEY = ? FLAG = ? @chal.route('/ctrime/encrypt/<plaintext>/') def encrypt(plaintext): plaintext = bytes.fromhex(plaintext) iv = int.from_bytes(os.urandom(16), 'big') cipher = AES.new(KEY, AES.MODE_CTR, counter=Counter.new(128, initial_value=iv)) encrypted = cipher.encrypt(zlib.compress(plaintext + FLAG.encode())) return {"ciphertext": encrypted.hex()} ``` Chall chỉ có 1 hàm encrypt, và trả về enrypted là mã hóa của đoạn nén plaintext + flag. Tính chất của zlib.compress là thực hiện nén các đoạn/ từ lặp lại với nhau và đánh mức độ từ 1-9, và zlib thường sẽ xét theo đoạn giống nhau ( > 3 bytes ) để xác định lặp lại. Nếu có các đoạn lặp lại thì sau khi nén sẽ giảm được bớt độ dài. CTR mode không làm thay đổi độ dài của ciphertext, tức là plaintext và ciphertext đều cùng độ dài. Vì thế có thể lợi dụng zlib.compress để brute-force các kí tự của flag, nếu như kí tự đó ở trong flag thì độ dài của bytes sẽ thay đổi ít nhất hoặc không thay đổi. Đây cũng là CRIME attack: https://hpc-notes.soton.ac.uk/talks/bullrun/Crime_slides.pdf **Note:** Vì tính chất của zlib.compress là xét theo đoạn từ 3 bytes trở lên để nén nên khi brute từng từ sẽ thấy ở chữ E tức sau chữ M độ dài sẽ tăng thêm, em có 2 hướng là tự đoán được sau chữ M là E và đặt luôn, hoặc là gửi đoạn ( flag + w ) lặp lại nhiều lần để tăng sự lặp lại và khiến zlib nhận ra . ### Script giải: ```python= import requests BASE = "https://aes.cryptohack.org/ctrime" def encrypt( pt ): url = f"{BASE}/encrypt/{pt}/" r = requests.get(url) data = r.json() return data["ciphertext"] alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789{}" flag = "crypto{" while ( "}" not in flag ): enc = encrypt( flag.encode().hex() ) if( flag.endswith( "M" ) ): flag += "E" continue for w in alphabet: pt = encrypt( ( flag + w ).encode().hex() ) if( len( pt ) == len( enc ) ): print( w ) flag += w break print( flag ) ``` ## Logon Zero ### Đề: ```python= #!/usr/bin/env python3 from Crypto.Cipher import AES from Crypto.Util.number import bytes_to_long from os import urandom from utils import listener FLAG = "crypto{???????????????????????????????}" class CFB8: def __init__(self, key): self.key = key def encrypt(self, plaintext): IV = urandom(16) cipher = AES.new(self.key, AES.MODE_ECB) ct = b'' state = IV for i in range(len(plaintext)): b = cipher.encrypt(state)[0] c = b ^ plaintext[i] ct += bytes([c]) state = state[1:] + bytes([c]) return IV + ct def decrypt(self, ciphertext): IV = ciphertext[:16] ct = ciphertext[16:] cipher = AES.new(self.key, AES.MODE_ECB) pt = b'' state = IV for i in range(len(ct)): b = cipher.encrypt(state)[0] c = b ^ ct[i] pt += bytes([c]) state = state[1:] + bytes([ct[i]]) return pt class Challenge(): def __init__(self): self.before_input = "Please authenticate to this Domain Controller to proceed\n" self.password = urandom(20) self.password_length = len(self.password) self.cipher = CFB8(urandom(16)) def challenge(self, your_input): if your_input['option'] == 'authenticate': if 'password' not in your_input: return {'msg': 'No password provided.'} your_password = your_input['password'] if your_password.encode() == self.password: self.exit = True return {'msg': 'Welcome admin, flag: ' + FLAG} else: return {'msg': 'Wrong password.'} if your_input['option'] == 'reset_connection': self.cipher = CFB8(urandom(16)) return {'msg': 'Connection has been reset.'} if your_input['option'] == 'reset_password': if 'token' not in your_input: return {'msg': 'No token provided.'} token_ct = bytes.fromhex(your_input['token']) if len(token_ct) < 28: return {'msg': 'New password should be at least 8-characters long.'} token = self.cipher.decrypt(token_ct) new_password = token[:-4] self.password_length = bytes_to_long(token[-4:]) self.password = new_password[:self.password_length] return {'msg': 'Password has been correctly reset.'} import builtins; builtins.Challenge = Challenge # hack to enable challenge to be run locally, see https://cryptohack.org/faq/#listener listener.start_server(port=13399) ``` Khi search trên mang Logon Zero attack thì em thấy đây là một CVE và thấy bài này [Link](https://www.sophos.com/en-us/blog/zerologon-hacking-windows-servers-with-a-bunch-of-zeros ![image](https://hackmd.io/_uploads/SyaFxeoFbl.png) Tức là với tính chất của CFB8-AES thì nếu ta gửi được token là chuỗi 8 số 0 thì sẽ có tỉ lệ trùng khớp với password = "" Trong đoạn code decrypt ```python= def decrypt(self, ciphertext): IV = ciphertext[:16] ct = ciphertext[16:] cipher = AES.new(self.key, AES.MODE_ECB) pt = b'' state = IV for i in range(len(ct)): b = cipher.encrypt(state)[0] c = b ^ ct[i] pt += bytes([c]) state = state[1:] + bytes([ct[i]]) return pt ``` Ta thấy là pt được tạo từ c, mà c thì được tạo từ b xor ct[i], tức là nếu ta gửi token = b"0"*28 thì c = b. Mà b là một trong 256 bytes, tỉ lệ là 1/256 b = b"\x00". Vì thế c là dãy byte toàn số 0. Cách tạo password và password length lại lấy dãy c. Nếu c là dãy toàn số 0 và password length được tạo từ 4 bytes cuối của c thì độ dài của password là 0, tức là chuỗi rỗng. ### Script giải: ```python= import json from pwn import * HOST = "socket.cryptohack.org" PORT = 13399 r = remote( HOST, PORT ) def send( data ): r.sendline( json.dumps( data ).encode() ) token = b"\x00" * 28 reset_password = { "option" : "reset_password", "token" : token.hex() } reset_connection = { "option" : "reset_connection", } auth = { "option" : "authenticate", "password" : "" } r.recvline() while True: send( reset_password ) r.recvline() send( auth ) r.recvuntil( b"msg" ) msg = r.recvline() if( b"flag" in msg ): print( msg ) sys.exit() else: send( reset_connection ) r.recvline() ``` ## Stream of Consciousness ```python= from Crypto.Cipher import AES from Crypto.Util import Counter import random KEY = ? TEXT = ['???', '???', ..., FLAG] @chal.route('/stream_consciousness/encrypt/') def encrypt(): random_line = random.choice(TEXT) cipher = AES.new(KEY, AES.MODE_CTR, counter=Counter.new(128)) encrypted = cipher.encrypt(random_line.encode()) return {"ciphertext": encrypted.hex()} ``` Ta thấy là hàm encrypt sẽ chọn random phần tử trong mảng TEXT và giải mã. Vì thế bước đầu em sẽ chạy và xem kích cỡ của mảng và thấy có khoảng 22 phần tử. ![image](https://hackmd.io/_uploads/HyDFugiFbe.png) ```python= for i in range( 100 ): data = int( encrypt(), 16 ) if( data not in ct ): ct.append( data ) with open( "ciphertext.txt", "w" ) as f: for i in ct: f.write( str( i ) + "\n" ) ``` Vì biết được format của flag là **crypto{** nên ta có thể brute được 7 bytes đầu của key ( đoạn sau khi đưa vào block encryption ) bằng cách xor flag với từng phần tử trong của TEXT sau khi mã hóa. Sau đó thực hiện xor key lại với tất cả phần tử để xem plaintext của tất cả, sau khi thực hiện và xem các key thì em thấy key dưới đây xor tạo được các plaintext đọc được. ![image](https://hackmd.io/_uploads/SyvU_liFZe.png) ```python= for i in ct: key = xor( flag, long_to_bytes( int( i )) ) print( " key: ", key ) for i in ct: print( xor( key, long_to_bytes( int( i )) ) ) ``` Sau đó chơi trò đoán từ tiếng anh để prefix và tìm lại key hoàn chỉnh :))) ### Script: ```python= import requests import json from Crypto.Util.number import long_to_bytes, bytes_to_long BASE = "https://aes.cryptohack.org/stream_consciousness" def encrypt(): url = f"{BASE}/encrypt/" r = requests.get(url) data = r.json() return data["ciphertext"] def xor( bytes1, bytes2 ): return bytes(a ^ b for a, b in zip(bytes1, bytes2)) flag = b"crypto{" ct = [] ''' Brute độ dài mảng TEXT for i in range( 100 ): data = int( encrypt(), 16 ) if( data not in ct ): ct.append( data ) with open( "ciphertext.txt", "w" ) as f: for i in ct: f.write( str( i ) + "\n" ) ''' with open( "ciphertext.txt", "r" ) as f: for line in f: ct.append( line.strip() ) ''' for i in ct: key = xor( flag, long_to_bytes( int( i )) ) print( " key: ", key ) for i in ct: print( xor( key, long_to_bytes( int( i )) ) ) ''' key = b'uN1\xce\xf4\x1d\xc5' ''' pt = [ "I shall", "Dress-m", "These h", "Love, p", "How pro", "No, I'l", "I'm unh", "crypto{", "As if I", "It can'", "Why do ", "Three b", "The ter", "Dolly w", "Our? Wh", "I shall", "But I w", "And I s", "What a ", "Perhaps", "Would I", "What a "] pt = ['I shall ', 'Dress-ma', 'These ho', 'Love, pr', 'How prou', "No, I'll", "I'm unha", 'crypto{k', 'As if I ', "It can't", 'Why do t', 'Three bo', 'The terr', 'Dolly wi', 'Our? Why', 'I shall,', 'But I wi', 'And I sh', 'What a l', 'Perhaps ', 'Would I ', 'What a n'] pt = ['I shall l', 'Dress-mak', 'These hor', 'Love, pro', 'How proud', "No, I'll ", "I'm unhap", 'crypto{k3', 'As if I h', "It can't ", 'Why do th', 'Three boy', 'The terri', 'Dolly wil', 'Our? Why ', 'I shall, ', 'But I wil', 'And I sha', 'What a lo', 'Perhaps h', 'Would I h', 'What a na'] pt = ['I shall los', 'Dress-makin', 'These horse', 'Love, proba', 'How proud a', "No, I'll go", "I'm unhappy", 'crypto{k3y5', 'As if I had', "It can't be", 'Why do they', 'Three boys ', 'The terribl', 'Dolly will ', 'Our? Why ou', "I shall, I'", 'But I will ', 'And I shall', 'What a lot ', 'Perhaps he ', 'Would I hav', 'What a nast'] pt = ['I shall lose e', 'Dress-making a', 'These horses, ', 'Love, probably', 'How proud and ', "No, I'll go in", "I'm unhappy, I", 'crypto{k3y57r3', 'As if I had an', "It can't be to", 'Why do they go', 'Three boys run', 'The terrible t', 'Dolly will thi', 'Our? Why our?', "I shall, I'll ", 'But I will sho', 'And I shall ig', 'What a lot of ', 'Perhaps he has', 'Would I have b', 'What a nasty s'] pt = ['I shall lose every', 'Dress-making and M', 'These horses, this', 'Love, probably? Th', 'How proud and happ', "No, I'll go in to ", "I'm unhappy, I des", 'crypto{k3y57r34m_r', 'As if I had any wi', "It can't be torn o", 'Why do they go on ', 'Three boys running', 'The terrible thing', 'Dolly will think t', 'Our? Why our?', "I shall, I'll lose", 'But I will show hi', 'And I shall ignore', 'What a lot of thin', 'Perhaps he has mis', 'Would I have belie', 'What a nasty smell'] pt = ['I shall lose everyth', 'Dress-making and Mil', 'These horses, this c', 'Love, probably? They', 'How proud and happy ', "No, I'll go in to Do", "I'm unhappy, I deser", 'crypto{k3y57r34m_r3u', 'As if I had any wish', "It can't be torn out", 'Why do they go on pa', 'Three boys running, ', 'The terrible thing i', 'Dolly will think tha', 'Our? Why our?', "I shall, I'll lose e", 'But I will show him.', 'And I shall ignore i', 'What a lot of things', 'Perhaps he has misse', 'Would I have believe', 'What a nasty smell t'] pt = ['I shall lose everything', 'Dress-making and Millin', 'These horses, this carr', 'Love, probably? They do', "How proud and happy he'", "No, I'll go in to Dolly", "I'm unhappy, I deserve ", 'crypto{k3y57r34m_r3u53_', 'As if I had any wish to', "It can't be torn out, b", 'Why do they go on paint', 'Three boys running, pla', 'The terrible thing is t', 'Dolly will think that I', 'Our? Why our?', "I shall, I'll lose ever", 'But I will show him.', 'And I shall ignore it.', 'What a lot of things th', 'Perhaps he has missed t', 'Would I have believed t', 'What a nasty smell this'] pt = ['I shall lose everything and n', 'Dress-making and Millinery', 'These horses, this carriage -', "Love, probably? They don't kn", "How proud and happy he'll be ", "No, I'll go in to Dolly and t", "I'm unhappy, I deserve it, th", 'crypto{k3y57r34m_r3u53_15_f47', 'As if I had any wish to be in', "It can't be torn out, but it ", 'Why do they go on painting an', 'Three boys running, playing a', 'The terrible thing is that th', "Dolly will think that I'm lea", 'Our? Why our?', "I shall, I'll lose everything", 'But I will show him.', 'And I shall ignore it.', 'What a lot of things that the', 'Perhaps he has missed the tra', 'Would I have believed then th', 'What a nasty smell this paint'] pt = ['I shall lose everything and not', 'Dress-making and Millinery', 'These horses, this carriage - h', "Love, probably? They don't know", "How proud and happy he'll be wh", "No, I'll go in to Dolly and tel", "I'm unhappy, I deserve it, the ", 'crypto{k3y57r34m_r3u53_15_f474l', 'As if I had any wish to be in t', "It can't be torn out, but it ca", 'Why do they go on painting and ', 'Three boys running, playing at ', 'The terrible thing is that the ', "Dolly will think that I'm leavi", 'Our? Why our?', "I shall, I'll lose everything i", 'But I will show him.', 'And I shall ignore it.', 'What a lot of things that then ', 'Perhaps he has missed the train', 'Would I have believed then that', 'What a nasty smell this paint h'] ''' pt = ['I shall lose everything and not g', 'Dress-making and Millinery', 'These horses, this carriage - how', "Love, probably? They don't know h", "How proud and happy he'll be when", "No, I'll go in to Dolly and tell ", "I'm unhappy, I deserve it, the fa", 'crypto{k3y57r34m_r3u53_15_f474l}', 'As if I had any wish to be in the', "It can't be torn out, but it can ", 'Why do they go on painting and bu', 'Three boys running, playing at ho', 'The terrible thing is that the pa', "Dolly will think that I'm leaving", 'Our? Why our?', "I shall, I'll lose everything if ", 'But I will show him.', 'And I shall ignore it.', 'What a lot of things that then se', 'Perhaps he has missed the train a', 'Would I have believed then that I', 'What a nasty smell this paint had'] pt[9] += "t" key = xor( pt[9].encode(), long_to_bytes( int( ct[9] ) ) ) pt[4] += "d" key = xor( pt[4].encode(), long_to_bytes( int( ct[4] ) ) ) pt[6] += "py" key = xor( pt[6].encode(), long_to_bytes( int( ct[6] ) ) ) pt[3] += "bly" key = xor( pt[3].encode(), long_to_bytes( int( ct[3] ) ) ) pt[17] += "nore" key = xor( pt[17].encode(), long_to_bytes( int( ct[17] ) ) ) pt[8] += "sh" key = xor( pt[8].encode(), long_to_bytes( int( ct[8] ) ) ) pt[0] += "ing" key = xor( pt[0].encode(), long_to_bytes( int( ct[0] ) ) ) pt[15] += "ything" key = xor( pt[15].encode(), long_to_bytes( int( ct[15] ) ) ) pt[19] += "in" key = xor( pt[19].encode(), long_to_bytes( int( ct[19] ) ) ) pt[13] += "ng" key = xor( pt[13].encode(), long_to_bytes( int( ct[13] ) ) ) for i in range ( len( ct ) ): pt[i] = xor( key, long_to_bytes( int( ct[i] ))).decode() print( "pt = ", pt ) ``` ## Dancing Queen ### Đề: ```python= #!/usr/bin/env python3 from os import urandom FLAG = b'crypto{?????????????????????????????}' def bytes_to_words(b): return [int.from_bytes(b[i:i+4], 'little') for i in range(0, len(b), 4)] def rotate(x, n): return ((x << n) & 0xffffffff) | ((x >> (32 - n)) & 0xffffffff) def word(x): return x % (2 ** 32) def words_to_bytes(w): return b''.join([i.to_bytes(4, 'little') for i in w]) def xor(a, b): return b''.join([bytes([x ^ y]) for x, y in zip(a, b)]) class ChaCha20: def __init__(self): self._state = [] def _inner_block(self, state): self._quarter_round(state, 0, 4, 8, 12) self._quarter_round(state, 1, 5, 9, 13) self._quarter_round(state, 2, 6, 10, 14) self._quarter_round(state, 3, 7, 11, 15) self._quarter_round(state, 0, 5, 10, 15) self._quarter_round(state, 1, 6, 11, 12) self._quarter_round(state, 2, 7, 8, 13) self._quarter_round(state, 3, 4, 9, 14) def _quarter_round(self, x, a, b, c, d): x[a] = word(x[a] + x[b]); x[d] ^= x[a]; x[d] = rotate(x[d], 16) x[c] = word(x[c] + x[d]); x[b] ^= x[c]; x[b] = rotate(x[b], 12) x[a] = word(x[a] + x[b]); x[d] ^= x[a]; x[d] = rotate(x[d], 8) x[c] = word(x[c] + x[d]); x[b] ^= x[c]; x[b] = rotate(x[b], 7) def _setup_state(self, key, iv): self._state = [0x61707865, 0x3320646e, 0x79622d32, 0x6b206574] self._state.extend(bytes_to_words(key)) self._state.append(self._counter) self._state.extend(bytes_to_words(iv)) def decrypt(self, c, key, iv): return self.encrypt(c, key, iv) def encrypt(self, m, key, iv): c = b'' self._counter = 1 for i in range(0, len(m), 64): self._setup_state(key, iv) for j in range(10): self._inner_block(self._state) c += xor(m[i:i+64], words_to_bytes(self._state)) self._counter += 1 return c if __name__ == '__main__': msg = b'Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula.' key = urandom(32) iv1 = urandom(12) iv2 = urandom(12) c = ChaCha20() msg_enc = c.encrypt(msg, key, iv1) flag_enc = c.encrypt(FLAG, key, iv2) print(f"iv1 = '{iv1.hex()}'") print(f"iv2 = '{iv2.hex()}'") print(f"msg_enc = '{msg_enc.hex()}'") print(f"flag_enc = '{flag_enc.hex()}'") ``` **Output.txt** ```txt! iv1 = 'e42758d6d218013ea63e3c49' iv2 = 'a99f9a7d097daabd2aa2a235' msg_enc = 'f3afbada8237af6e94c7d2065ee0e221a1748b8c7b11105a8cc8a1c74253611c94fe7ea6fa8a9133505772ef619f04b05d2e2b0732cc483df72ccebb09a92c211ef5a52628094f09a30fc692cb25647f' flag_enc = 'b6327e9a2253034096344ad5694a2040b114753e24ea9c1af17c10263281fb0fe622b32732' ``` Việc cần làm là tìm được key , key được giấu trong state. ```python= def decrypt(self, c, key, iv): return self.encrypt(c, key, iv) def encrypt(self, m, key, iv): c = b'' self._counter = 1 for i in range(0, len(m), 64): self._setup_state(key, iv) for j in range(10): self._inner_block(self._state) c += xor(m[i:i+64], words_to_bytes(self._state)) self._counter += 1 return c ``` Ta thấy hàm decrypt và encrypt là một, và c ở hàm encrypt được tạo từ việc xor từng block 64 bytes m với state. Và đề đã cho một đoạn plaintext cùng với ciphertext và IV của nó, có thể tìm lại được state của block đầu bằng việc xor block đầu của plaintext với ciphertext msg. Sau khi tìm được state thì thực hiện đảo ngược quá trình để tìm lại state ban đầu. ```python= def rev_rotate( x, n ): return (( x >> n ) & 0xffffffff ) | (( x << ( 32 - n ) ) & 0xffffffff ) def rev_inner_block( state ): rev_quarter_round(state, 3, 4, 9, 14) rev_quarter_round(state, 2, 7, 8, 13) rev_quarter_round(state, 1, 6, 11, 12) rev_quarter_round(state, 0, 5, 10, 15) rev_quarter_round(state, 3, 7, 11, 15) rev_quarter_round(state, 2, 6, 10, 14) rev_quarter_round(state, 1, 5, 9, 13) rev_quarter_round(state, 0, 4, 8, 12) return state def rev_quarter_round( x, a, b, c, d): x[b] = rev_rotate(x[b], 7); x[b] ^= x[c]; x[c] = word(x[c] - x[d]) x[d] = rev_rotate(x[d], 8); x[d] ^= x[a]; x[a] = word(x[a] - x[b]) x[b] = rev_rotate(x[b], 12); x[b] ^= x[c]; x[c] = word(x[c] - x[d]) x[d] = rev_rotate(x[d], 16); x[d] ^= x[a]; x[a] = word(x[a] - x[b]) ``` Sau khi tìm được state thì có thể tìm được key vì ta biết sau key là counter = 1, trước key có 4 phần tử cố định. Chuyển đoạn key đó sang bytes và decrypt sẽ được flag. ### Script giải: ```python= def rotate(x, n): return ((x << n) & 0xffffffff) | ((x >> (32 - n)) & 0xffffffff) class ChaCha20: def __init__(self): self._state = [] def _inner_block(self, state): self._quarter_round(state, 0, 4, 8, 12) self._quarter_round(state, 1, 5, 9, 13) self._quarter_round(state, 2, 6, 10, 14) self._quarter_round(state, 3, 7, 11, 15) self._quarter_round(state, 0, 5, 10, 15) self._quarter_round(state, 1, 6, 11, 12) self._quarter_round(state, 2, 7, 8, 13) self._quarter_round(state, 3, 4, 9, 14) def _quarter_round(self, x, a, b, c, d): x[a] = word(x[a] + x[b]); x[d] ^= x[a]; x[d] = rotate(x[d], 16) x[c] = word(x[c] + x[d]); x[b] ^= x[c]; x[b] = rotate(x[b], 12) x[a] = word(x[a] + x[b]); x[d] ^= x[a]; x[d] = rotate(x[d], 8) x[c] = word(x[c] + x[d]); x[b] ^= x[c]; x[b] = rotate(x[b], 7) def _setup_state(self, key, iv): self._state = [0x61707865, 0x3320646e, 0x79622d32, 0x6b206574] self._state.extend(bytes_to_words(key)) self._state.append(self._counter) self._state.extend(bytes_to_words(iv)) def decrypt(self, c, key, iv): return self.encrypt(c, key, iv) def encrypt(self, m, key, iv): c = b'' self._counter = 1 for i in range(0, len(m), 64): self._setup_state(key, iv) for j in range(10): self._inner_block(self._state) c += xor(m[i:i+64], words_to_bytes(self._state)) self._counter += 1 return c def rev_rotate( x, n ): return (( x >> n ) & 0xffffffff ) | (( x << ( 32 - n ) ) & 0xffffffff ) def word(x): return x % (2 ** 32) def words_to_bytes(w): return b''.join([i.to_bytes(4, 'little') for i in w]) def xor(a, b): return b''.join([bytes([x ^ y]) for x, y in zip(a, b)]) def bytes_to_words(b): return [int.from_bytes(b[i:i+4], 'little') for i in range(0, len(b), 4)] def rev_inner_block( state ): rev_quarter_round(state, 3, 4, 9, 14) rev_quarter_round(state, 2, 7, 8, 13) rev_quarter_round(state, 1, 6, 11, 12) rev_quarter_round(state, 0, 5, 10, 15) rev_quarter_round(state, 3, 7, 11, 15) rev_quarter_round(state, 2, 6, 10, 14) rev_quarter_round(state, 1, 5, 9, 13) rev_quarter_round(state, 0, 4, 8, 12) return state def rev_quarter_round( x, a, b, c, d): x[b] = rev_rotate(x[b], 7); x[b] ^= x[c]; x[c] = word(x[c] - x[d]) x[d] = rev_rotate(x[d], 8); x[d] ^= x[a]; x[a] = word(x[a] - x[b]) x[b] = rev_rotate(x[b], 12); x[b] ^= x[c]; x[c] = word(x[c] - x[d]) x[d] = rev_rotate(x[d], 16); x[d] ^= x[a]; x[a] = word(x[a] - x[b]) iv1 = b"\xe4'X\xd6\xd2\x18\x01>\xa6><I" iv2 = b'\xa9\x9f\x9a}\t}\xaa\xbd*\xa2\xa25' msg_enc = b'\xf3\xaf\xba\xda\x827\xafn\x94\xc7\xd2\x06^\xe0\xe2!\xa1t\x8b\x8c{\x11\x10Z\x8c\xc8\xa1\xc7BSa\x1c\x94\xfe~\xa6\xfa\x8a\x913PWr\xefa\x9f\x04\xb0].+\x072\xccH=\xf7,\xce\xbb\t\xa9,!\x1e\xf5\xa5&(\tO\t\xa3\x0f\xc6\x92\xcb%d\x7f' flag_enc = b'\xb62~\x9a"S\x03@\x964J\xd5iJ @\xb1\x14u>$\xea\x9c\x1a\xf1|\x10&2\x81\xfb\x0f\xe6"\xb3\'2' msg = b'Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula.' xor1 = xor( msg[ : 64 ], msg_enc[ : 64 ] ) xor2 = xor( msg[ 64: ], msg_enc[ 64: ] ) state1 = bytes_to_words( xor1 ) state2 = bytes_to_words( xor2 ) for j in range ( 10 ): state1 = rev_inner_block( state1 ) key = [] for i in range( 4, len( state1 ) ): if( state1[i] == 1 ): break key.append( state1[i] ) key = words_to_bytes( key ) c = ChaCha20() flag = c.decrypt( flag_enc, key, iv2 ) print( flag.decode() ) ``` ## Pad Thai ### Đề: ```python= #!/usr/bin/env python3 from Crypto.Util.Padding import unpad from Crypto.Cipher import AES from os import urandom from utils import listener FLAG = 'crypto{?????????????????????????????????????????????????????}' class Challenge: def __init__(self): self.before_input = "Let's practice padding oracle attacks! Recover my message and I'll send you a flag.\n" self.message = urandom(16).hex() self.key = urandom(16) def get_ct(self): iv = urandom(16) cipher = AES.new(self.key, AES.MODE_CBC, iv=iv) ct = cipher.encrypt(self.message.encode("ascii")) return {"ct": (iv+ct).hex()} def check_padding(self, ct): ct = bytes.fromhex(ct) iv, ct = ct[:16], ct[16:] cipher = AES.new(self.key, AES.MODE_CBC, iv=iv) pt = cipher.decrypt(ct) # does not remove padding try: unpad(pt, 16) except ValueError: good = False else: good = True return {"result": good} def check_message(self, message): if message != self.message: self.exit = True return {"error": "incorrect message"} return {"flag": FLAG} # # This challenge function is called on your input, which must be JSON # encoded # def challenge(self, msg): if "option" not in msg or msg["option"] not in ("encrypt", "unpad", "check"): return {"error": "Option must be one of: encrypt, unpad, check"} if msg["option"] == "encrypt": return self.get_ct() elif msg["option"] == "unpad": return self.check_padding(msg["ct"]) elif msg["option"] == "check": return self.check_message(msg["message"]) import builtins; builtins.Challenge = Challenge # hack to enable challenge to be run locally, see https://cryptohack.org/faq/#listener listener.start_server(port=13421) ``` Đây là bài padding oracle attack vì đề bảo rứa :)))), và khi search tài liệu thì em thấy bài viết của anh L1ttl3 https://hackmd.io/@L1ttl3NoPro/HJrtwWBdJx cùng với vidieo https://www.youtube.com/watch?v=4EgD4PEatA8&t=693s&ab_channel=BrianYen Vì thấy bài viết và video giải thích rất kĩ và rất dễ hiểu nên em không biết viết gì về bài này, chỉ cần thực hiện padding oracle attack là sẽ ra :))) ### Script giải: ```python= from pwn import * import json import re HOST = "socket.cryptohack.org" PORT = 13421 r = remote( HOST, PORT ) def recv(): line = r.recvline().decode().strip() match = re.search( r"(\{.*\})", line ) if not match: return None return json.loads( match.group(1) ) def send( data ): r.sendline( json.dumps(data).encode() ) encrypt = { "option" : "encrypt" } r.recvline() send( encrypt ) ciphertext = recv()["ct"] def xor( bytes1, bytes2 ): return bytes(a ^ b for a, b in zip(bytes1, bytes2)) size = 16 def attack_block( ct, idx ): block = ct[ idx * 16 : ( idx + 1 ) * 16 ] prev = ct[ ( idx - 1 ) * 16 : idx * 16 ] I = b"" xor_text = b"" for i in range( size - 1, -1, -1 ): xor_text = xor( I, bytes([ size - i ]) * ( size - i - 1 ) ) for byte in range ( 256 ): data = prev[ : i ] + bytes( [ byte ] ) + xor_text + block unpad = { "option" : "unpad", "ct" : data.hex() } send( unpad ) result = recv()["result"] if( result ): I = xor( bytes([ byte ]), bytes([ size - i ])) + I break return xor( I, prev ) def attack( ciphertext ): ciphertext = bytes.fromhex( ciphertext ) pt = b"" for i in range( ( len( ciphertext ) // size ) - 1, 0, -1 ): pt = attack_block( ciphertext, i ) + pt return pt msg = attack( ciphertext ) check = { "option" : "check", "message" : msg.decode() } send( check ) flag = r.recv() print( flag ) ``` ## Ngoài các mode CBC, ECB, OFB, CTR còn có GCM, CCM. Còn ChaCha20, RC4 Phần này em chưa tìm hiểu nên sẽ note đây và bổ sung sau. https://hackmd.io/@nomorecaffeine/SkzPhT2R3#RC4-v%C3%A0-FMS-attack https://hackmd.io/@Thangcoithongminh/ry3owiLX3 https://protonvpn.com/blog/chacha20?srsltid=AfmBOorJ0ZSu_Enw25LMu0lhxbr1SSSEoDEbOJH4weq8ybWUOKaao0Xw ## Vài kiểu tấn công khác https://wrth.medium.com/cracking-aes-without-any-one-of-its-operations-c42cdfc0452f

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password
    or
    Sign in via Facebook Sign in via X(Twitter) Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    By signing in, you agree to our terms of service.

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully