RS256 กับ HS256: ความแตกต่างคืออะไร?


254

ฉันใช้ Auth0 เพื่อจัดการการพิสูจน์ตัวตนในแอปพลิเคชันเว็บของฉัน ฉันใช้ ASP.NET Core v1.0.0 และ Angular 2 rc5 และฉันไม่ค่อยรู้เกี่ยวกับการรับรองความถูกต้อง / ความปลอดภัยโดยทั่วไป

ในเอกสาร Auth0 สำหรับ ASP.NET Core Web Apiมีสองตัวเลือกสำหรับอัลกอริทึม JWT คือ RS256 และ HS256 นี่อาจเป็นคำถามที่โง่ แต่:

ความแตกต่างระหว่าง RS256 และ HS256 คืออะไร? บางกรณีการใช้งาน (ถ้ามี) คืออะไร?


ฉันพบ Angular2-JWT พร้อม firbase / php-Jwt ที่แอปพลิเคชันฝั่งเซิร์ฟเวอร์กวดวิชาfreakyjolly.com/…
รหัส Spy

คำตอบ:


437

ตัวเลือกทั้งสองอ้างถึงอัลกอริธึมที่ผู้ให้บริการเอกลักษณ์ใช้เพื่อลงชื่อ JWT การลงนามเป็นการดำเนินการเข้ารหัสลับที่สร้าง "ลายเซ็น" (ส่วนหนึ่งของ JWT) ที่ผู้รับโทเค็นสามารถตรวจสอบเพื่อให้แน่ใจว่าโทเค็นไม่ได้ถูกดัดแปลง

  • RS256 (RSA Signature พร้อมSHA-256 ) เป็นอัลกอริธึมแบบอสมมาตรและใช้คู่คีย์สาธารณะ / ส่วนตัว: ผู้ให้บริการเอกลักษณ์มีคีย์ส่วนตัว (ความลับ) ที่ใช้ในการสร้างลายเซ็นและผู้บริโภคของ JWT ได้รับกุญแจสาธารณะ เพื่อตรวจสอบลายเซ็น เนื่องจากคีย์สาธารณะซึ่งตรงข้ามกับคีย์ส่วนตัวนั้นไม่จำเป็นต้องได้รับการรักษาความปลอดภัยผู้ให้บริการข้อมูลส่วนใหญ่จึงทำให้ผู้ใช้สามารถรับและใช้งานได้ง่าย (โดยปกติผ่าน URL เมทาดาทา)

  • ในทางกลับกัน HS256 ( HMACกับ SHA-256) เกี่ยวข้องกับการรวมกันของฟังก์ชั่นการแฮชและคีย์หนึ่ง (ลับ) ที่ใช้ร่วมกันระหว่างทั้งสองฝ่ายที่ใช้ในการสร้างแฮชที่จะทำหน้าที่เป็นลายเซ็น เนื่องจากมีการใช้คีย์เดียวกันทั้งคู่ในการสร้างลายเซ็นและตรวจสอบความถูกต้องจึงต้องใช้ความระมัดระวังเพื่อให้แน่ใจว่าคีย์จะไม่ถูกทำลาย

หากคุณจะพัฒนาแอปพลิเคชันที่ใช้ JWT คุณสามารถใช้ HS256 ได้อย่างปลอดภัยเพราะคุณจะสามารถควบคุมได้ว่าใครใช้คีย์ลับ ในทางกลับกันหากคุณไม่สามารถควบคุมลูกค้าได้หรือคุณไม่มีวิธีการรักษาความปลอดภัยของรหัสลับ RS256 จะเหมาะกว่าเนื่องจากผู้บริโภคต้องการทราบรหัสสาธารณะ (สาธารณะ) เท่านั้น

เนื่องจากโดยทั่วไปแล้วจะใช้พับลิกคีย์สาธารณะจากจุดปลายเมทาดาทาลูกค้าสามารถตั้งโปรแกรมให้เรียกใช้พับลิกคีย์อัตโนมัติได้ หากเป็นกรณีนี้ (เช่นเดียวกับในไลบรารี. Net Core) คุณจะมีงานทำน้อยกว่าในการกำหนดค่า (ไลบรารีจะดึงกุญแจสาธารณะจากเซิร์ฟเวอร์) ในทางกลับกันกุญแจแบบสมมาตรจะต้องทำการแลกเปลี่ยนนอกแบนด์วิดท์ (เพื่อให้แน่ใจว่ามีช่องทางการสื่อสารที่ปลอดภัย) และอัปเดตด้วยตนเองหากมีคีย์การลงนาม

Auth0 จัดเตรียมจุดสิ้นสุดเมทาดาทาสำหรับโปรโตคอล OIDC, SAML และ WS-Fed ซึ่งสามารถดึงกุญแจสาธารณะได้ คุณสามารถเห็นจุดสิ้นสุดเหล่านั้นภายใต้ "การตั้งค่าขั้นสูง" ของลูกค้า

ปลายทาง OIDC https://{account domain}/.well-known/openid-configurationเมตัวอย่างเช่นจะใช้รูปแบบของ หากคุณเรียกดู URL นั้นคุณจะเห็นวัตถุ JSON ที่มีการอ้างอิงถึงhttps://{account domain}/.well-known/jwks.jsonซึ่งมีรหัสสาธารณะ (หรือกุญแจ) ของบัญชี

หากคุณดูตัวอย่าง RS256 คุณจะเห็นว่าคุณไม่จำเป็นต้องกำหนดค่าพับลิกคีย์ที่ใดก็ได้: กรอบจะถูกดึงมาโดยอัตโนมัติ


46
หมายเหตุเมื่อใช้ rs256 - มี (หรือเคย) มีความเสี่ยงด้านความปลอดภัยในไลบรารีจำนวนมากซึ่งอนุญาตให้โทเค็นสามารถกำหนดอัลกอริทึมที่จะใช้ โดยพื้นฐานแล้วผู้โจมตีสามารถใช้คีย์ rs256 สาธารณะกับการเข้ารหัส hs256 เพื่อทำเป็นว่ามันเป็นรหัสลับ ดังนั้นตรวจสอบให้แน่ใจว่าห้องสมุดของคุณไม่มีพฤติกรรมนี้!
AlexFoxGill

7
การแก้ไขเล็ก ๆ อย่างหนึ่ง "HS256 (HMAC กับ SHA-256) เป็นอัลกอริทึมแบบสมมาตร" - HMAC ไม่ได้ใช้อัลกอริทึมแบบสมมาตร - คีย์ (ซึ่งจะช่วยให้คุณเข้ารหัสและถอดรหัสลายเซ็นโดยคำจำกัดความ) มันใช้ฟังก์ชันแฮชเข้ารหัสลับและคีย์การเข้ารหัสลับใต้HMAC ซึ่งหมายถึงการคำนวณของแฮช (ฟังก์ชันทางเดียว) เหนือข้อความด้วยคีย์ลับต่อท้าย
Kikoz

1
ตัวอย่างกับ Google: ไปที่accounts.google.com/.well-known/openid-configurationและดู jwks_uri; ส่งต่อให้คุณไปที่googleapis.com/oauth2/v3/certsซึ่งคุณสามารถหากุญแจได้ จากนั้นคุณก็ต้องดึงกุญแจที่ดีจากลูกของมัน
เดนิส TRUFFAUT

น่าสังเกตว่าในขณะที่ HS256 แบ่งปันคีย์ระหว่างสองฝ่ายที่ทำให้อัลกอริทึมนี้ไม่สามารถรองรับผู้ชมหลายคนใน access_token เดียวในขณะที่ RS256 สามารถรองรับผู้ชมจำนวนมากได้ สิ่งนี้เกี่ยวข้องกับไคลเอ็นต์ประจำตัวเช่น Auth0 ที่อนุญาตเฉพาะการร้องขอไปยัง / userinfo ปลายทางโดยใช้ access_token หากการกำหนดค่าเป็น RS256 เนื่องจากพวกเขาต้องการโทเค็นนี้เพื่อสนับสนุนโดเมน api ของคุณในฐานะผู้ดูแลระบบรวมถึงโดเมน auth0 ของพวกเขา
jezpez

94

ในการเข้ารหัสมีอัลกอริทึมสองประเภทที่ใช้:

อัลกอริทึมแบบสมมาตร

ใช้รหัสเดียวเพื่อเข้ารหัสข้อมูล เมื่อเข้ารหัสด้วยกุญแจข้อมูลสามารถถอดรหัสได้โดยใช้รหัสเดียวกัน ตัวอย่างเช่นหาก Mary เข้ารหัสข้อความโดยใช้คีย์ "my-secret" และส่งไปยัง John เขาจะสามารถถอดรหัสข้อความได้อย่างถูกต้องด้วยคีย์เดียวกัน "my-secret"

อัลกอริทึมแบบไม่สมมาตร

มีใช้สองปุ่มเพื่อเข้ารหัสและถอดรหัสข้อความ ในขณะที่ใช้หนึ่งคีย์ (สาธารณะ) เพื่อเข้ารหัสข้อความคีย์อื่น (ส่วนตัว) สามารถใช้เพื่อถอดรหัสเท่านั้น ดังนั้นจอห์นสามารถสร้างทั้งกุญแจสาธารณะและกุญแจส่วนตัวจากนั้นส่งเฉพาะกุญแจสาธารณะไปยัง Mary เพื่อเข้ารหัสข้อความของเธอ ข้อความสามารถถอดรหัสได้โดยใช้คีย์ส่วนตัว

สถานการณ์จำลอง HS256 และ RS256

อัลกอริทึมเหล่านี้ไม่ได้ใช้ในการเข้ารหัส / ถอดรหัสข้อมูล ค่อนข้างจะใช้ในการตรวจสอบที่มาหรือความถูกต้องของข้อมูล เมื่อ Mary ต้องการส่งข้อความเปิดถึง Jhon และเขาต้องการตรวจสอบว่าข้อความนั้นมาจาก Mary อย่างแน่นอนสามารถใช้ HS256 หรือ RS256 ได้

HS256 สามารถสร้างลายเซ็นสำหรับตัวอย่างของข้อมูลที่กำหนดโดยใช้คีย์เดียว เมื่อข้อความถูกส่งพร้อมกับลายเซ็นผู้รับที่ได้รับสามารถใช้รหัสเดียวกันเพื่อตรวจสอบว่าลายเซ็นตรงกับข้อความ

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


38

ประสิทธิภาพการทำงานแตกต่างกัน

เพียงแค่ใส่HS256ขนาดประมาณ 1 คำสั่งเร็วกว่าRS256สำหรับการตรวจสอบ แต่ประมาณ 2 คำสั่งขนาดเร็วกว่าRS256สำหรับการออก (การเซ็น)

 640,251  91,464.3 ops/s
  86,123  12,303.3 ops/s (RS256 verify)
   7,046   1,006.5 ops/s (RS256 sign)

อย่าไปคิดเลขจริง ๆ ลองคิดดูด้วยความเคารพซึ่งกันและกัน

[Program.cs]

class Program
{
    static void Main(string[] args)
    {
        foreach (var duration in new[] { 1, 3, 5, 7 })
        {
            var t = TimeSpan.FromSeconds(duration);

            byte[] publicKey, privateKey;

            using (var rsa = new RSACryptoServiceProvider())
            {
                publicKey = rsa.ExportCspBlob(false);
                privateKey = rsa.ExportCspBlob(true);
            }

            byte[] key = new byte[64];

            using (var rng = new RNGCryptoServiceProvider())
            {
                rng.GetBytes(key);
            }

            var s1 = new Stopwatch();
            var n1 = 0;

            using (var hs256 = new HMACSHA256(key))
            {
                while (s1.Elapsed < t)
                {
                    s1.Start();
                    var hash = hs256.ComputeHash(privateKey);
                    s1.Stop();
                    n1++;
                }
            }

            byte[] sign;

            using (var rsa = new RSACryptoServiceProvider())
            {
                rsa.ImportCspBlob(privateKey);

                sign = rsa.SignData(privateKey, "SHA256");
            }

            var s2 = new Stopwatch();
            var n2 = 0;

            using (var rsa = new RSACryptoServiceProvider())
            {
                rsa.ImportCspBlob(publicKey);

                while (s2.Elapsed < t)
                {
                    s2.Start();
                    var success = rsa.VerifyData(privateKey, "SHA256", sign);
                    s2.Stop();
                    n2++;
                }
            }

            var s3 = new Stopwatch();
            var n3 = 0;

            using (var rsa = new RSACryptoServiceProvider())
            {
                rsa.ImportCspBlob(privateKey);

                while (s3.Elapsed < t)
                {
                    s3.Start();
                    rsa.SignData(privateKey, "SHA256");
                    s3.Stop();
                    n3++;
                }
            }

            Console.WriteLine($"{s1.Elapsed.TotalSeconds:0} {n1,7:N0} {n1 / s1.Elapsed.TotalSeconds,9:N1} ops/s");
            Console.WriteLine($"{s2.Elapsed.TotalSeconds:0} {n2,7:N0} {n2 / s2.Elapsed.TotalSeconds,9:N1} ops/s");
            Console.WriteLine($"{s3.Elapsed.TotalSeconds:0} {n3,7:N0} {n3 / s3.Elapsed.TotalSeconds,9:N1} ops/s");

            Console.WriteLine($"RS256 is {(n1 / s1.Elapsed.TotalSeconds) / (n2 / s2.Elapsed.TotalSeconds),9:N1}x slower (verify)");
            Console.WriteLine($"RS256 is {(n1 / s1.Elapsed.TotalSeconds) / (n3 / s3.Elapsed.TotalSeconds),9:N1}x slower (issue)");

            // RS256 is about 7.5x slower, but it can still do over 10K ops per sec.
        }
    }
}

ตัวเลขเหล่านี้มีความสำคัญ ขอบคุณ. ฉันมักจะคิดว่าการเข้ารหัสเป็นการส่งผ่านข้อมูลที่โปร่งใสมากขึ้นหรือน้อยลง แต่งานวิจัยของคุณบอกเป็นนัยว่าการใช้ R256 เพื่อลงนามการสื่อสารระหว่างเครื่องจะเพิ่ม 1 ms ต่อ hop
Matthew Mark Miller

1
@MatthewMarkMiller โปรดจำไว้ว่ามันไม่เท่ากันในการใช้งาน พวกเขามีลักษณะแตกต่างกัน RS256 นั้นไม่สมมาตรดังนั้นในการสื่อสารสไตล์ไคลเอนต์ / เซิร์ฟเวอร์ที่คุณแชร์คีย์สาธารณะเท่านั้นจึงเป็นตัวเลือกที่ดีกว่า HS256 ต้องการการแชร์คีย์ที่สามารถลงชื่อและยืนยันได้ - มีประโยชน์เฉพาะเมื่อคุณเชื่อถือทั้งสองฝ่ายหรือไม่ต้องการให้ฝ่ายใดฝ่ายหนึ่งถอดรหัสอะไร
Rob Evans

7
@RobEvans ใช่ไม่ต้องหยุดยั้งตัวเลขประสิทธิภาพที่นี่ เลือกวิธีแก้ปัญหาที่เหมาะสมกับปัญหาของคุณ นี่เป็นเพียงการสังเกตไม่ใช่ข้อเสนอแนะที่จะสนับสนุน HS256 ผ่าน RS256 คุณต้องทำการตัดสินใจตามบริบทของคุณ
John Leidegren

1
เมื่อตัวเลือกโปรโตคอลสามารถส่งผลกระทบต่อความหน่วงเช่นเดียวกับสายเคเบิลเพิ่มเติมอีกกิโลเมตรนั่นเป็นสิ่งที่ควรค่าแก่การรู้
Matthew Mark Miller

0

คำตอบสั้น ๆ เฉพาะ OAuth2

  • HS256ความลับไคลเอ็นต์ผู้ใช้เพื่อสร้างโทเค็นลายเซ็นและต้องใช้ความลับเดียวกันเพื่อตรวจสอบโทเค็นในแบ็คเอนด์ ดังนั้นคุณควรมีสำเนาของความลับนั้นในเซิร์ฟเวอร์แบ็คเอนด์ของคุณเพื่อตรวจสอบลายเซ็น
  • RS256ใช้การเข้ารหัสพับลิกคีย์เพื่อลงนามโทเค็นลายเซ็น (แฮช) จะสร้างขึ้นโดยใช้ไพรเวตคีย์และสามารถตรวจสอบได้โดยใช้พับลิกคีย์ ดังนั้นไม่จำเป็นต้องใช้กุญแจส่วนตัวหรือความลับของลูกค้าในการจัดเก็บในเซิร์ฟเวอร์ด้านหลัง แต่เซิร์ฟเวอร์ส่วนหลังจะดึงกุญแจสาธารณะจาก URL การกำหนดค่า openid ในผู้เช่าของคุณ ( https: // [tenant] /.well-known/openid - การกำหนดค่า ) เพื่อตรวจสอบโทเค็น พารามิเตอร์ KID ภายใน access_toekn จะใช้เพื่อตรวจจับคีย์ที่ถูกต้อง (สาธารณะ) จาก openid-configuration
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.