Java String เป็น SHA1


158

ฉันกำลังพยายามสร้างตัวแปลงสตริงเป็น SHA1 อย่างง่ายใน Java และนี่คือสิ่งที่ฉันมี ...

public static String toSHA1(byte[] convertme) {
    MessageDigest md = null;
    try {
        md = MessageDigest.getInstance("SHA-1");
    }
    catch(NoSuchAlgorithmException e) {
        e.printStackTrace();
    } 
    return new String(md.digest(convertme));
}

เมื่อฉันผ่านมันtoSHA1("password".getBytes())ฉันได้รับ[�a�ɹ??�%l�3~��.ฉันรู้ว่ามันอาจจะมีการแก้ไขการเข้ารหัสง่ายๆเช่น UTF-8 แต่บางคนอาจบอกฉันว่าฉันควรจะทำอย่างไรที่จะได้รับสิ่งที่ฉันต้องการซึ่งเป็น5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8? หรือว่าฉันทำสิ่งนี้ผิดอย่างสิ้นเชิง?


อัลกอริทึมนั้นSHA1ไม่มีเครื่องหมายขีดคั่นไม่ทราบว่าจะสร้างความแตกต่างหรือไม่
The Scrum Meister

เป็นการดีที่จะระบุการเข้ารหัสอักขระเมื่อคุณโทรgetBytes()เช่นใช้toSHA1("password".getBytes("UTF-8"))
Qwerky

คำตอบ:


183

อัพเดท
คุณสามารถใช้Apache Commons Codec (รุ่น 1.7 ขึ้นไป) เพื่อทำงานนี้ให้คุณ

DigestUtils.sha1Hex (stringToConvertToSHexRepresentation)

ขอบคุณ @ Jon Onstottสำหรับคำแนะนำนี้


คำตอบเก่า
แปลง Byte Array ของคุณเป็น Hex String จริงของวิธีการจะบอกคุณว่า

return byteArrayToHexString(md.digest(convertme))

และ (คัดลอกมาจากวิธีการของ Real)

public static String byteArrayToHexString(byte[] b) {
  String result = "";
  for (int i=0; i < b.length; i++) {
    result +=
          Integer.toString( ( b[i] & 0xff ) + 0x100, 16).substring( 1 );
  }
  return result;
}

BTW คุณอาจได้รับขนาดกะทัดรัดมากขึ้นโดยใช้ Base64 Apache Commons Codec API 1.4มียูทิลิตี้ที่ดีที่จะกำจัดความเจ็บปวดทั้งหมด อ้างอิงที่นี่


4
base64 และ sha1 นั้นแตกต่างกันมาก - ไม่แนะนำให้ใช้เป็นทางเลือก
Ryan A.

13
@ RyanA .: ตามที่ฉันเข้าใจเขาแนะนำ base64 เป็นทางเลือกแทนการเข้ารหัสแฮช SHA1 แบบ hex (ไม่ใช่ทางเลือกแทน SHA1 ทั้งหมด)
helmbert

ฉันยังไม่ได้ลองเลย แต่คุณอยากจะอธิบายวิธีการทำงานนี้หรือไม่?
Jivay

11
ทำไมไม่ใช้ห้องสมุดเช่นDigestUtils.sha1Hex("my string")แทนที่จะสร้างล้อใหม่ (ถึงแม้ว่ามันจะน่าสนใจที่จะรู้วิธีการแปลงเป็นเลขฐานสิบหกด้วยมือ)?
Jon Onstott

3
เพราะเมื่อคำตอบนี้ถูกเขียน DigestUtils (1.7 วางจำหน่ายในเดือนกันยายน 2012) ไม่มีคุณสมบัตินั้น ขอบคุณที่ชี้นำสิ่งนี้ +1
Nishant

67

นี่เป็นทางออกของการแปลงสตริงเป็น sha1 มันทำงานได้ดีในแอพ Android ของฉัน:

private static String encryptPassword(String password)
{
    String sha1 = "";
    try
    {
        MessageDigest crypt = MessageDigest.getInstance("SHA-1");
        crypt.reset();
        crypt.update(password.getBytes("UTF-8"));
        sha1 = byteToHex(crypt.digest());
    }
    catch(NoSuchAlgorithmException e)
    {
        e.printStackTrace();
    }
    catch(UnsupportedEncodingException e)
    {
        e.printStackTrace();
    }
    return sha1;
}

private static String byteToHex(final byte[] hash)
{
    Formatter formatter = new Formatter();
    for (byte b : hash)
    {
        formatter.format("%02x", b);
    }
    String result = formatter.toString();
    formatter.close();
    return result;
}

7
อาจต้องการระบุว่านี่คือ java.util.Formatter และต้องการ formatter.close () ที่ส่วนท้ายเพื่อหลีกเลี่ยงการเตือน
Eric Chen

Shoudln't encryptPassword("test")และecho test|sha1sumในลินุกซ์เอาท์พุทขั้วผลเดียวกันได้หรือไม่ พวกเขาทำไม่ได้
Tulains Córdova

@ TulainsCórdovaเกี่ยวกับคอนโซลภาวนา: ถ้าคุณใช้การส่งออกรวมทั้งแบ่งบรรทัดจะได้รับการประปาecho test หากคุณต้องการที่จะสับสตริงธรรมดาโดยไม่ต้องต่อท้ายแบ่งบรรทัดคุณสามารถใช้sha1sum พารามิเตอร์ทำให้ถนัดแบ่งสาย echo -n test | sha1sum-necho
MrSnrub

คำถามน้อยลง แต่โดยทั่วไปแล้ว: encryptPassword()เสียงของคุณเหมือนกับที่ใช้สำหรับเก็บข้อมูลการตรวจสอบสิทธิ์ โปรดทราบว่าการเข้ารหัสของคุณมีความเสี่ยงต่อการโจมตีด้วยพจนานุกรม ตรวจสอบสภาพแวดล้อมความปลอดภัยของคุณว่าเป็นปัญหาสำหรับแอปพลิเคชันของคุณหรือไม่!
EagleRainbow


32

SHA-1 (และอัลกอริทึมการแฮชอื่น ๆ ) จะส่งคืนข้อมูลไบนารี นั่นหมายความว่าพวกเขาผลิตกbyte[]. ที่byteอาเรย์ไม่ได้เป็นตัวแทนของตัวละครที่เฉพาะเจาะจงใด ๆ ซึ่งหมายความว่าคุณไม่สามารถเพียงแค่ทำให้มันกลายเป็นStringเช่นคุณได้

หากคุณต้องการ a Stringคุณต้องทำการฟอร์แมตbyte[]ด้วยวิธีที่สามารถแสดงเป็น a String(ไม่เช่นนั้นให้เก็บไว้byte[]รอบ ๆ )

วิธีทั่วไปสองวิธีในการใช้แทนbyte[]อักขระโดยพลการเนื่องจากอักขระที่พิมพ์ได้คือ BASE64 หรือ hex-Strings แบบง่าย ๆ (เช่นแทนแต่ละตัวbyteด้วยเลขฐานสิบหกสองหลัก) ดูเหมือนว่าคุณกำลังพยายามสร้าง hex-String

นอกจากนี้ยังมีอันตรายอื่น: ถ้าคุณต้องการที่จะได้รับ SHA-1 ของ Java Stringแล้วคุณจะต้องแปลงที่Stringไปbyte[]ครั้งแรก (ในขณะที่ใส่ของ SHA-1 เป็นbyte[]เช่นกัน) หากคุณเพียงแค่ใช้myString.getBytes()ตามที่คุณแสดงมันจะใช้การเข้ารหัสเริ่มต้นของแพลตฟอร์มและเช่นนั้นจะขึ้นอยู่กับสภาพแวดล้อมที่คุณใช้งาน (ตัวอย่างเช่นมันอาจส่งคืนข้อมูลที่แตกต่างกันตามการตั้งค่าภาษา / โลแคลของระบบปฏิบัติการ)

ทางออกที่ดีคือการระบุการเข้ารหัสเพื่อใช้สำหรับString-to- แปลงเช่นนี้byte[] myString.getBytes("UTF-8")การเลือก UTF-8 (หรือการเข้ารหัสอื่นที่สามารถแทนอักขระ Unicode ทุกตัว) เป็นตัวเลือกที่ปลอดภัยที่สุดที่นี่


27

นี่เป็นโซลูชันง่ายๆที่สามารถใช้เมื่อแปลงสตริงเป็นรูปแบบฐานสิบหก:

private static String encryptPassword(String password) throws NoSuchAlgorithmException, UnsupportedEncodingException {

    MessageDigest crypt = MessageDigest.getInstance("SHA-1");
    crypt.reset();
    crypt.update(password.getBytes("UTF-8"));

    return new BigInteger(1, crypt.digest()).toString(16);
}

คำเตือน: การสร้างแฮชไม่ถูกต้องสำหรับแฮชที่ขึ้นต้นด้วย '0' คุณจะได้รับสายอักขระ 39 ตัว
philn

@ ฟิลน์คุณช่วยแนะนำทางออกได้ไหม?
Nikita Koksharov

1
ฉันเดาว่าถ้าคุณสร้างจำนวนเต็มขนาดใหญ่จากไบต์ [] ที่มีศูนย์นำหน้าพอ 0 เหล่านี้จะหายไป ดังนั้นการเป็นตัวแทนสตริง hex "0" จะไม่อยู่ที่นั่นนำไปสู่การแฮชที่มี 39 หรือน้อยกว่าตัวอักษร ผมใช้วิธีการแก้ปัญหาดังกล่าวข้างต้น petrnohejls และทำงานดี ...
philn

25

เพียงแค่ใช้ apache คอมมอนส์ตัวแปลงสัญญาณห้องสมุด พวกเขามีคลาสยูทิลิตี้ที่เรียกว่าDigestUtils

ไม่จำเป็นต้องลงรายละเอียด


51
ฉันไม่เห็นด้วยการรับรายละเอียดเป็นจุดทั้งหมด
เก้า

12
คำถามคือว่าคุณมีเวลาในการรับรายละเอียดหรือไม่ จุดรวมมักจะทำให้เสร็จในเวลาไม่ทุกคนเป็นนักเรียนหรือมีความหรูหราในการเรียนรู้รายละเอียดทั้งหมด
DaTroop

DigestUtils ส่งคืนอาร์เรย์ไบต์เพื่อรับการแทนค่าสตริงที่คุณต้องการเรียกใช้ผ่าน Hex.encodeHexString จาวา: มันเป็นปี 2014 และเรายังไม่มีวิธีการขั้นตอนเดียว
ryber

5
วิธีการ SHA-1 ขั้นตอนเดียว: String result = DigestUtils.sha1Hex("An input string"); o)
Jon Onstott

18

ดังกล่าวก่อนการใช้งาน apache คอมมอนส์ตัวแปลงสัญญาณ ก็แนะนำโดยพวกฤดูใบไม้ผลิเช่นกัน (ดู DigestUtils ใน Spring doc) เช่น:

DigestUtils.sha1Hex(b);

แน่นอนจะไม่ใช้คำตอบที่ได้รับความนิยมสูงสุดที่นี่


7

มันไม่ได้พิมพ์อย่างถูกต้องเพราะคุณต้องใช้การเข้ารหัส Base64 ด้วย Java 8 คุณสามารถเข้ารหัสโดยใช้คลาสตัวเข้ารหัสBase64

public static String toSHA1(byte[] convertme) {
    md = MessageDigest.getInstance("SHA-1");
    return Base64.getEncoder().encodeToString((md.digest(convertme));
}

ผลลัพธ์

สิ่งนี้จะให้ผลลัพธ์ที่คุณคาดหวังจาก 5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8


1
@Devenv คือ SHA-1 จุดทั้งสามหมายความว่ามันจะรักษารหัสเดิมของเขาซึ่งแปลงเป็น sha1 ปัญหาดั้งเดิมของ OP เกิดขึ้นเมื่อพิมพ์สตริงอย่างถูกต้อง
Eduardo Dennis

4

Message Digest (hash) is byte [] in byte [] out

ข้อความสรุปถูกกำหนดให้เป็นฟังก์ชั่นที่รับอาร์เรย์ไบต์ดิบและส่งกลับอาร์เรย์ไบต์ดิบ (aka byte[]) ตัวอย่างเช่นSHA-1 (Secure Hash Algorithm 1)มีขนาดย่อย 160 บิตหรือ 20 ไบต์ อาร์เรย์ไบต์แบบ Raw มักไม่สามารถตีความได้ว่าเป็นการเข้ารหัสอักขระเช่นUTF-8เพราะไม่ใช่ทุกไบต์ในทุกคำสั่งเป็นกฎหมายที่เข้ารหัส ดังนั้นการแปลงให้เป็นString:

new String(md.digest(subject), StandardCharsets.UTF_8)

อาจสร้างลำดับที่ผิดกฎหมายบางอย่างหรือมีตัวชี้รหัสไปยังการแมปUnicode ที่ไม่ได้กำหนด:

[�a�ɹ??�%l3~��.

การเข้ารหัสแบบไบนารีเป็นข้อความ

สำหรับการเข้ารหัสแบบไบนารีต่อข้อความนั้นใช้ ด้วย hashes หนึ่งที่ถูกนำมาใช้มากที่สุดคือการเข้ารหัส HEX หรือ Base16 โดยทั่วไปไบต์สามารถมีค่าจาก0ไป255(หรือ-128ที่จะ127ลงนาม) ซึ่งเทียบเท่ากับการเป็นตัวแทนของ HEX -0x00 0xFFดังนั้น hex จะเพิ่มความยาวของเอาต์พุตที่ต้องการเป็นสองเท่านั่นหมายความว่าเอาต์พุต 20 ไบต์จะสร้างสตริง hex ยาว 40 อักขระเช่น

2fd4e1c67a2d28fced849ee1bb76e7391b93eb12

โปรดทราบว่าไม่จำเป็นต้องใช้การเข้ารหัสฐานสิบหก นอกจากนี้คุณยังสามารถใช้สิ่งที่ต้องการbase64 Hex มักเป็นที่ต้องการเนื่องจากมนุษย์สามารถอ่านได้ง่ายขึ้นและมีความยาวเอาต์พุตที่กำหนดโดยไม่ต้องใช้ช่องว่างภายใน

คุณสามารถแปลงอาร์เรย์ไบต์เป็นฐานสิบหกด้วยฟังก์ชันการทำงาน JDK เพียงอย่างเดียว:

new BigInteger(1, token).toString(16)

อย่างไรก็ตามโปรดทราบว่าBigIntegerจะตีความอาร์เรย์ไบต์ที่กำหนดเป็นตัวเลขและไม่เป็นสตริงไบต์ นั่นหมายความว่าเลขศูนย์นำหน้าจะไม่ถูกเอาต์พุตและสตริงผลลัพธ์อาจสั้นกว่า 40 ตัวอักษร

ใช้ไลบรารีเพื่อเข้ารหัสเป็น HEX

ตอนนี้คุณสามารถคัดลอกและวางวิธีการไบต์ต่อฐานสิบหกยังไม่ทดลองจากกองมากเกินหรือใช้อ้างอิงขนาดใหญ่เหมือนฝรั่ง

หากต้องการมีโซลูชัน go-to สำหรับปัญหาที่เกี่ยวข้องกับไบต์ส่วนใหญ่ฉันได้ติดตั้งยูทิลิตีเพื่อจัดการกับกรณีเหล่านี้: bytes-java (Github)

ในการแปลงอาร์เรย์ย่อยข้อความของคุณคุณสามารถทำได้

String hex = Bytes.wrap(md.digest(subject)).encodeHex();

หรือคุณสามารถใช้คุณสมบัติแฮชในตัว

String hex =  Bytes.from(subject).hashSha1().encodeHex();

2

การเป็นตัวแทนฐาน 64 ของSHA1แฮช:

String hashedVal = Base64.getEncoder().encodeToString(DigestUtils.sha1(stringValue.getBytes(Charset.forName("UTF-8"))));

1

เหตุผลที่มันไม่ทำงานคือเมื่อคุณโทรString(md.digest(convertme))คุณกำลังบอก Java ให้ตีความลำดับของไบต์ที่เข้ารหัสเป็นสตริง สิ่งที่คุณต้องการคือการแปลงไบต์เป็นอักขระเลขฐานสิบหก


0

แปลงอาร์เรย์ไบต์เป็นสตริงฐานสิบหก

public static String toSHA1(byte[] convertme) {
    final char[] HEX_CHARS = "0123456789ABCDEF".toCharArray();
    MessageDigest md = null;
    try {
        md = MessageDigest.getInstance("SHA-1");
    }
    catch(NoSuchAlgorithmException e) {
        e.printStackTrace();
    }
    byte[] buf = md.digest(convertme);
    char[] chars = new char[2 * buf.length];
    for (int i = 0; i < buf.length; ++i) {
        chars[2 * i] = HEX_CHARS[(buf[i] & 0xF0) >>> 4];
        chars[2 * i + 1] = HEX_CHARS[buf[i] & 0x0F];
    }
    return new String(chars);
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.