การแฮชรหัสผ่าน underhanded [ปิด]


33

ด้วยจิตวิญญาณของการประกวด C ที่มีเล่ห์เหลี่ยม วัตถุประสงค์ของการประกวดครั้งนี้คือการนำโค้ดไปใช้อย่างตรงไปตรงมาในขณะที่ซ่อนจุดบกพร่องที่ซ่อนอยู่ในนั้นไว้อย่างละเอียด

การแข่งขัน

คุณเป็นโมลรัสเซียที่ทำงานในแผนก IT ของ บริษัท สายลับอเมริกัน เจ้านายชาวอเมริกันของคุณเพิ่งขอให้คุณใช้อัลกอริทึมการแฮชรหัสผ่านเพื่อใช้สำหรับการเข้ารหัสข้อความลับ

เจ้านายของคุณต้องการให้คุณใช้งานฟังก์ชั่นต่อไปนี้:

f: String -> byte[16]

ซึ่งแปลงรหัสผ่านเป็นปริมาณ 16 ไบต์ที่เหมาะสมสำหรับใช้เป็นคีย์ AES เจ้านายของคุณต้องการฟังก์ชั่นที่ปลอดภัยซึ่งในบริบทนี้หมายความว่าสตริงรหัสผ่านที่แตกต่างกันควรสร้างผลลัพธ์ที่แตกต่างพร้อมความน่าจะเป็นอย่างมาก ยกตัวอย่างเช่นกลับmd5fกัญชาของท่านจะเป็นการดำเนินงานที่เรียบง่ายของ

แน่นอนว่าหัวหน้าที่แท้จริงของคุณในสายลับรัสเซียต้องการให้คุณล้มล้างกระบวนการนี้ งานของคุณคือการดำเนินการดังกล่าวว่ารัสเซียสามารถถอดรหัสทุกข้อความลับเข้ารหัสโดยใช้ปุ่มส่งกลับโดยff

ในการทำเช่นนั้นคุณต้องดำเนินการfเพื่อที่จะส่งคืนชุดย่อยขนาดเล็กของเอาต์พุตที่เป็นไปได้ 2 ^ 128 เท่านั้น โดยเฉพาะอย่างยิ่งคุณfจะต้องส่งกลับผลลัพธ์ที่ต่างกันมากที่สุด 2 ^ 16 เพื่อให้รัสเซียสามารถค้นหาคีย์ที่ถูกต้องสำหรับข้อความที่เข้ารหัสแต่ละข้อความที่พวกเขาต้องการถอดรหัสได้อย่างง่ายดาย

อย่างไรก็ตามโปรดจำไว้ว่าการจารกรรมถือโทษประหารชีวิต เพื่อไม่ให้ถูกจับได้ฟังก์ชันของคุณfจะต้องสร้างผลลัพธ์ที่แตกต่างกันอย่างน้อย 2 ^ 8 ดังนั้นการตรวจสอบคร่าวๆของเอาต์พุตไม่กี่รายการfจึงไม่น่าจะเปิดเผยสิ่งที่ซ้ำกัน และที่สำคัญที่สุดรหัสที่คุณแนะนำเพื่อ จำกัด ช่วงของfต้องดูโดยไม่ได้ตั้งใจไม่ได้ตั้งใจ หากคุณถูกลากเข้าไปในห้องพิจารณาคดีต้องมีข้อสงสัยที่สมเหตุสมผลว่าคุณแนะนำข้อผิดพลาดโดยเจตนาหรือโดยอุบัติเหตุ

การตัดสิน

ฉันและอีกสองคนที่ฉันรับสมัครจะเป็นผู้ตัดสินผลงาน (ส่งอีเมลถึงฉันหากคุณต้องการตัดสิน) ฉันเสนอรางวัล 200 ชื่อเสียงให้กับผู้ชนะ ส่งจะต้องอัปโหลดภายในวันที่ 1 พฤษภาคม

การตัดสินจะพิจารณาเกณฑ์ต่อไปนี้:

  • ไม่fไปตามสเปคคือไม่ได้สร้างระหว่าง 2 ^ 8 และ 2 ^ 16 ผลที่เป็นไปได้ อย่ารู้สึกว่าสิ่งเหล่านี้มีข้อ จำกัด อย่างหนัก แต่เราจะหักคะแนนถ้าคุณอยู่ไกลเกินไป
  • ข้อผิดพลาดนั้นเป็นผลมาจากความผิดพลาดโดยไม่ตั้งใจหรือไม่?
  • ผลลัพธ์ของการfสุ่มดูหรือไม่
  • ยิ่งการใช้งานของคุณสั้นลงเท่าไหร่fก็ยิ่งดีเท่านั้น
  • ยิ่งการดำเนินการของคุณชัดเจนยิ่งfขึ้น

หมายเหตุ

คุณสามารถใช้ภาษาใดก็ได้เพื่อใช้รหัสของคุณ คุณกำลังพยายามซ่อนจุดบกพร่องในสายตาธรรมดาดังนั้นไม่แนะนำให้ใช้รหัสที่ทำให้งงงวย

คุณอาจต้องการดูผู้ชนะการประกวด Underhanded Cก่อนหน้านี้บางคนเพื่อทำความเข้าใจกับสิ่งที่ส่งผลดี

สตริงอินพุตจะพิมพ์ได้ ascii (32 ถึง 126, รวม) คุณอาจใช้ความยาวสูงสุดที่เหมาะสมถ้าคุณต้องการ


1
มีข้อ จำกัด ใด ๆ ในสายป้อน? เหมือนมันประกอบด้วยตัวอักษรเท่านั้น?
Ali1S232

@Gajet: คุณต้องจัดการกับอักขระ ASCII ที่พิมพ์ได้ทั้งหมด (32 ถึง 126 รวมอยู่ด้วย)
Keith Randall

เอาต์พุตอยู่ในช่วง 16- ไบต์สตริงทั้งหมดหรือเพียงแค่พิมพ์ได้หรือไม่
บูธตาม

@boothby: ค่า 16 ไบต์ที่เป็นไปได้ทั้งหมด (2 ^ 128 ความเป็นไปได้)
Keith Randall

1
ฉันลงคะแนนให้ปิดคำถามนี้เป็นหัวข้อนอกเพราะความท้าทายที่ไม่ได้อยู่ในหัวข้อบนเว็บไซต์นี้อีกต่อไป meta.codegolf.stackexchange.com/a/8326/20469
cat

คำตอบ:


15

C

2 ^ 16 เอาต์พุตที่เป็นไปได้ (หรือ 2 ^ 8 เท่าของจำนวนอักขระที่ใช้)
ใช้การปรับใช้ MD5 ของ Linux ซึ่งก็คือ AFAIK แต่นี่จะให้แฮชเดียวกันเช่น "40" และ "42"
แก้ไข: เปลี่ยนชื่อbcopyเป็นmemcpy(เปลี่ยนพารามิเตอร์ของหลักสูตร)
แก้ไข: แปลงจากโปรแกรมเป็นฟังก์ชันเพื่อตอบสนองความต้องการที่ดีขึ้น

#include <string.h>
#include <openssl/md5.h>

void f(const unsigned char *input, unsigned char output[16]) {

    /* Put the input in a 32-byte buffer, padded with zeros. */
    unsigned char workbuf[32] = {0};
    strncpy(workbuf, input, sizeof(workbuf));

    unsigned char res[MD5_DIGEST_LENGTH];
    MD5(workbuf, sizeof(workbuf), res);

    /* NOTE: MD5 has known weaknesses, so using it isn't 100% secure.
     * To compensate, prefix the input buffer with its own MD5, and hash again. */
    memcpy(workbuf+1, workbuf, sizeof(workbuf)-1);
    workbuf[0] = res[0];
    MD5(workbuf, sizeof(workbuf), res);

    /* Copy the result to the output buffer */
    memcpy(output, res, 16);
}

/* Some operating systems don't have memcpy(), so include a simple implementation */
void *
memcpy(void *_dest, const void *_src, size_t n)
{
    const unsigned char *src = _src;
    unsigned char *dest = _dest;
    while (n--) *dest++ = *src++;
    return _dest;
}

นี่เป็นข้อบกพร่องของ MD5 หรือไม่
Ali1S232

@Gajet, ไม่ฉันใช้ MD5 ของ Linux ซึ่งก็โอเคอย่างสมบูรณ์ (AFAIK)
ugoren

ทำไมมันไม่สร้างผลลัพธ์ที่เป็นไปได้มากขึ้น?
Ali1S232

1
@Gajet: พิจารณาสิ่งที่เกิดขึ้นในbcopyขั้นตอน ... มันเป็นความผิดพลาดที่ดีเนื่องจากbcopyฟังก์ชั่นBSD จริงจะทำงานได้อย่างถูกต้องที่นี่
ฮัน

@ อันที่จริงตอนนี้ฉันเห็นแล้วว่าbcopyเป็นรถของฉัน ฉันจะเปลี่ยนเป็นmemcpyแล้วการดำเนินการเดียวกันจะใช้ได้
ugoren

13

C

นี่อาจไม่ใช่รายการประกวดที่ฉูดฉาด แต่ฉันคิดว่าต่อไปนี้เป็นฟังก์ชันแฮชที่ coder ใด ๆ ที่ฉลาดเกินกว่าจะทำเพื่อประโยชน์ของพวกเขาเองได้

#include <stdio.h>
#include <string.h>
#include <stdint.h>

void hash(const char* s, uint8_t* r, size_t n)
{
     uint32_t h = 123456789UL;
     for (size_t i = 0; i < n; i++) {
          for (const char* p = s; *p; p++) {
               h = h * 33 + *p;
          }
          *r++ = (h >> 3) & 0xff;
          h = h ^ 987654321UL;
     }
}

int main()
{
     size_t n = 1024;
     char s[n];
     size_t m = 16;
     uint8_t b[m];
     while (fgets(s, n, stdin)) {
          hash(s, b, m);
          for (size_t i = 0; i < m; ++i)
               printf("%02x", b[i]);
          printf("\n");
     }
}

ในความเป็นจริงฟังก์ชั่นแฮชสามารถส่งคืนผลลัพธ์ที่แตกต่างไม่เกิน L * 2048 โดยที่ L คือจำนวนความยาวสตริงที่แตกต่างกันที่สามารถเกิดขึ้นได้ ในทางปฏิบัติฉันทดสอบโค้ดบนบรรทัดอินพุตที่ไม่ซ้ำกัน 1.85 ล้านบรรทัดจากหน้าคู่มือและเอกสาร html บนแล็ปท็อปของฉันและมีแฮชเฉพาะ 85428 ที่แตกต่างกัน


0

Scala:

// smaller values for more easy tests:
val len = 16
// make a 16 bytes fingerprint
def to16Bytes (l: BigInt, pos: Int=len) : List[Byte] = 
  if (pos == 1) List (l.toByte) else (l % 256L).toByte :: to16Bytes (l / 256L, pos-1)
/** if number isn't prime, take next */
def nextProbPrime (l: BigInt) : BigInt = 
  if (l.isProbablePrime (9)) l else nextProbPrime (l + 1)
/** Take every input, shift and add, but take primes */
def codify (s: String): BigInt = 
  (BigInt (17) /: s) ((a, b) => nextProbPrime (a * BigInt (257) + b))
/** very, very short Strings - less than 14 bytes - have to be filled, to obscure them a bit: */
def f (s: String) : Array [Byte] = {
  val filled = (if (s.size < 14) s + "secret" + s else s)
  to16Bytes (codify (filled + filled.reverse)).toArray.map (l => nextProbPrime (l).toByte) 
}

ทดสอบถ้าผลลัพธ์ไม่เหมือนกันสำหรับอินพุตที่คล้ายกัน:

val samples = List ("a", "aa", "b", "", "longer example", "This is a foolish, fishy test") 

samples.map (f) 

 List[Array[Byte]] = List(
Array (-41, -113, -79, 127, 29, 127, 31, 67, -19, 83, -73, -31, -101, -113, 97, -113), 
Array (-19, 7, -43, 89, -97, -113, 47, -53, -113, -127, -31, -113, -67, -23, 127, 127), 
Array (-41, -113, -79, 127, 29, 127, 31, 67, -19, 83, -73, -31, -101, -113, 97, -113), 
Array (37, -19, -7, 67, -83, 89, 59, -11, -23, -47, 97, 83, 19, 2, 2, 2), 
Array (79, 101, -47, -103, 47, -13, 29, -37, -83, -3, -37, 59, 127, 97, -43, -43), 
Array (37, 53, -43, -73, -67, 5, 11, -89, -37, -103, 107, 97, 37, -71, 59, 67))

ข้อผิดพลาดคือการใช้เฉพาะช่วงเวลาสำหรับการเข้ารหัส แทน

scala> math.pow (256, 16)
res5: Double = 3.4028236692093846E38

ค่าเราจบด้วย

scala> math.pow (54, 16)
res6: Double = 5.227573613485917E27

เนื่องจากมี 54 ช่วงเวลาต่ำกว่า 256


2
5.22e27 >> 2^16. ไม่มีทางที่จะดุร้ายกับความเป็นไปได้มากมาย
Keith Randall

คุณลืมชื่อภาษา
ajax333221

@ ajax333221: Scala ฉันเพิ่มมันไปด้านบน
ผู้ใช้ไม่รู้จัก

@ KeithRandall: ฉันสามารถ 'บังเอิญ' ใช้ Bytes เชิงบวกเท่านั้นซึ่งจะลดความเป็นไปได้ในการ math.pow (27, 16) แต่นั่นก็ยังคงเป็น 8e22
ผู้ใช้ที่ไม่รู้จัก
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.