java لماذا يفضل char[] على String لكلمات المرور؟




security passwords (14)

في Swing ، يحتوي حقل كلمة المرور على getPassword() (إرجاع أسلوب char[] ) بدلاً من الأسلوب getText() (إرجاع String ) المعتاد. وبالمثل ، فقد صادفت اقتراحًا بعدم استخدام String للتعامل مع كلمات المرور.

لماذا تشكل String تهديدًا للأمان عندما يتعلق الأمر بكلمات المرور؟ يشعر غير مريح لاستخدام char[] .


هذه هي جميع الأسباب ، يجب على المرء اختيار شار [] مجموعة بدلا من سلسلة لكلمة المرور.

1. بما أن السلاسل تكون غير قابلة للتغيير في Java إذا قمت بتخزين كلمة المرور كنص عادي ، فسوف تكون متوفرة في الذاكرة حتى يزيلها جامع القمامة ، وبما أن String تستخدم في String pool من أجل إعادة الاستخدام ، فهناك احتمال كبير بأن تبقى في الذاكرة لفترة طويلة المدة التي تشكل تهديدًا أمنيًا. نظرًا لأن أي شخص لديه إمكانية الوصول إلى ملف تفريغ الذاكرة يمكنه العثور على كلمة المرور بنص واضح وهذا سبب آخر يجب أن يستخدم دائمًا كلمة مرور مشفرة بدلاً من النص العادي. بما أن السلاسل قابلة للتغيير ، فلا يمكن تغيير محتويات السلاسل نظرًا لأن أي تغيير سيؤدي إلى سلسلة جديدة ، بينما إذا قمت بالتمرير [] ، فلا يزال بإمكانك تعيين كل عنصره على أنه فارغ أو صفر. لذا فإن تخزين كلمة المرور في صفيف الحروف يخفف بوضوح من مخاطر الأمان لسرقة كلمة المرور.

2. توصي Java نفسها باستخدام أسلوب getPassword () الخاص بـ JPasswordField والذي يقوم بإرجاع char [] وأسلوب getText () الذي تم إيقافه والذي يقوم بإرجاع كلمة المرور بنص واضح يوضح سبب الأمان. من الجيد اتباع النصائح من فريق جافا والالتزام بالمعيار بدلاً من الاعتراض عليه.

3. مع سلسلة هناك دائما خطر من طباعة نص عادي في ملف السجل أو وحدة التحكم ولكن إذا كنت تستخدم صفيف لن تقوم بطباعة محتويات الصفيف بدلاً من طباعة موقع الذاكرة الخاصة به. على الرغم من عدم وجود سبب حقيقي ولكن لا يزال من المنطقي.

    String strPassword="Unknown";
    char[] charPassword= new char[]{'U','n','k','w','o','n'};
    System.out.println("String password: " + strPassword);
    System.out.println("Character password: " + charPassword);

    String password: Unknown
    Character password: [[email protected]

مرجع من: http://javarevisited.blogspot.com/2012/03/why-character-array-is-better-than.html نأمل أن يساعد هذا.


السلسلة غير قابلة للتغيير وتذهب إلى تجمع السلسلة. بمجرد كتابتها ، لا يمكن الكتابة فوقها.

char[] عبارة عن مصفوفة يجب استبدالها بمجرد استخدام كلمة المرور ، وهذه هي الطريقة التي ينبغي أن يتم بها:

char[] passw = request.getPassword().toCharArray()
if (comparePasswords(dbPassword, passw) {
 allowUser = true;
 cleanPassword(passw);
 cleanPassword(dbPassword);
 passw=null;
}

private static void cleanPassword (char[] pass) {
 for (char ch: pass) {
  ch = null;
 }
}

أحد السيناريوهات التي يمكن أن يستخدمها المهاجم هو تحطم طائرة - عند تعطل JVM وإنشاء ملف تفريغ ذاكرة - ستتمكن من رؤية كلمة المرور.

هذا ليس بالضرورة مهاجم خارجي ضار. يمكن أن يكون هذا مستخدم دعم لديه وصول إلى الخادم لأغراض المراقبة. يمكنه النظر إلى تحطم الطائرة والعثور على كلمات المرور.


لاقتباس مستند رسمي ، فإن دليل Java Cryptography Architecture يقول هذا عن char[] مقابل كلمات مرور String (حول التشفير المستند إلى كلمة المرور ، ولكن هذا بشكل عام حول كلمات المرور بالطبع):

قد يبدو من المنطقي جمع كلمة المرور وتخزينها في كائن من النوع java.lang.String . ومع ذلك ، هنا التحذير: Object s من النوع String غير قابل للتغيير ، أي ، لا توجد طرق محددة تسمح لك بتغيير (الكتابة) أو صفر خارج محتويات String بعد الاستخدام. تجعل هذه الميزة كائنات String غير مناسبة لتخزين المعلومات الحساسة للأمان مثل كلمات مرور المستخدم. يجب عليك دائمًا جمع وتخزين المعلومات الحساسة للأمان في مجموعة char بدلاً من ذلك.

التوجيهي 2-2 من "إرشادات الترميز الآمن" لـ Java Programming Language ، الإصدار 4.0 أيضاً يقول شيئاً مشابهاً (على الرغم من أنه في الأصل في سياق التسجيل):

المبدأ التوجيهي 2-2: لا تسجل المعلومات الحساسة للغاية

بعض المعلومات ، مثل أرقام الضمان الاجتماعي (SSN) وكلمات المرور ، حساسة للغاية. يجب ألا يتم الاحتفاظ بهذه المعلومات لمدة أطول من اللازم أو حيث يمكن رؤيتها ، حتى من قبل المسؤولين. على سبيل المثال ، لا يجب إرسالها إلى ملفات السجل ، ويجب ألا يكون وجودها قابلاً للاكتشاف من خلال عمليات البحث. قد يتم الاحتفاظ ببعض البيانات العابرة في هياكل بيانات قابلة للتغيير ، مثل صفائف char ، ويتم مسحها فورًا بعد الاستخدام. لقد أدى تقليل بنية البيانات إلى تقليل الفاعلية في أنظمة تشغيل Java النموذجية حيث يتم نقل الكائنات في الذاكرة بشفافية إلى المبرمج.

كما أن هذا المبدأ التوجيهي له آثاره على تنفيذ واستخدام المكتبات ذات المستوى الأدنى التي ليس لديها معرفة دلالية بالبيانات التي تتعامل معها. على سبيل المثال ، قد تقوم مكتبة تحليل سلسلة ذات مستوى منخفض بتسجيل النص الذي يعمل عليه. يمكن للتطبيق تحليل SSN مع المكتبة. هذا يخلق وضعا حيث SSNs متاحة للمسؤولين مع الوصول إلى ملفات السجل.


سلاسل غير قابلة للتغيير . وهذا يعني أنه بمجرد إنشاء String ، إذا تمكنت عملية أخرى من تفريغ الذاكرة ، فلا توجد طريقة (بصرف النظر عن reflection ) يمكنك التخلص من البيانات قبل بدء تجميع البيانات المهملة .

باستخدام مصفوفة ، يمكنك مسح البيانات بشكل صريح بعد الانتهاء منها. يمكنك استبدال الصفيف بأي شيء تريده ، ولن تكون كلمة المرور موجودة في أي مكان في النظام ، حتى قبل جمع البيانات المهملة.

لذلك نعم ، هذا هو مصدر قلق أمني - ولكن حتى باستخدام char[] فقط يقلل من نافذة الفرصة لمهاجم ، وهذا فقط لهذا النوع المحدد من الهجوم.

كما هو مذكور في التعليقات ، من المحتمل أن المصفوفات التي يتم نقلها من قبل جامع البيانات المهملة ستترك نسخًا ضالة من البيانات في الذاكرة. أعتقد أن هذا الأمر يتعلق بالتنفيذ - قد يقوم جامع القمامة بتنظيف كل الذاكرة كما هو ، لتجنب هذا النوع من الأشياء. حتى لو كان كذلك ، لا يزال هناك الوقت الذي يحتوي فيه char[] على الأحرف الفعلية كنافذة هجوم.


  1. سلاسل غير قابلة للتغيير في Java إذا قمت بتخزين كلمة المرور كنص عادي سوف تكون متوفرة في الذاكرة حتى مسح جامع القمامة وحيث يتم استخدام سلاسل في تجمع السلسلة من أجل إعادة الاستخدام هناك فرصة عالية جدا أنه سيبقى في الذاكرة لفترة طويلة المدة التي تشكل تهديدًا أمنيًا. نظرًا لأن أي شخص لديه إمكانية الوصول إلى تفريغ الذاكرة يمكنه العثور على كلمة المرور بنص واضح
  2. توصية Java باستخدام أسلوب getPassword() والذي يقوم بإرجاع char [] وأسلوب getText() الذي يقوم بإرجاع كلمة المرور بنص واضح يوضح سبب الأمان.
  3. toString () هناك دائما خطر من طباعة نص عادي في ملف السجل أو وحدة التحكم ولكن إذا كنت تستخدم صفيف لن تقوم بطباعة محتويات الصفيف بدلاً من طباعة موقع الذاكرة الخاص به.

    String strPwd = "passwd";
    char[] charPwd = new char[]{'p','a','s','s','w','d'};
    System.out.println("String password: " + strPwd );
    System.out.println("Character password: " + charPwd );
    

    كلمة المرور السلسلة: passwd

    كلمة مرور الشخصية: [C @ 110b2345

الأفكار الأخيرة: على الرغم من أن استخدام char [] لا يكفي فقط لمسح المحتوى ليكون أكثر أمانًا. أقترح أيضًا العمل مع كلمة المرور المجزأة أو المشفرة بدلاً من النص العادي ومسحها من الذاكرة بمجرد اكتمال المصادقة.


1) بما أن السلاسل تكون غير قابلة للتغيير في Java إذا قمت بتخزين كلمة المرور كنص عادي ، فستكون متاحة في الذاكرة حتى يزيلها جامع القمامة ، وبما أن String يستخدم في String pool من أجل إعادة الاستخدام ، فهناك احتمال كبير جدًا أن يبقى في الذاكرة لفترة طويلة ، الأمر الذي يشكل تهديدًا أمنيًا. نظرًا لأن أي شخص لديه إمكانية الوصول إلى ملف تفريغ الذاكرة يمكنه العثور على كلمة المرور بنص واضح وهذا سبب آخر يجب أن تستخدم دائمًا كلمة مرور مشفرة بدلاً من النص العادي. نظرًا لأن السلاسل غير قابلة للتغيير ، فلا توجد طريقة لتغيير محتويات السلاسل نظرًا لأن أي تغيير سيؤدي إلى إنشاء سلسلة جديدة ، بينما إذا كنت char [] ، فلا يزال بإمكانك تعيين كل عنصره على أنه فارغ أو صفر. لذا ، فإن تخزين كلمة المرور في مصفوفة الأحرف يخفف من مخاطر سرقة كلمة المرور.

2) توصي Java نفسها باستخدام أسلوب getPassword () الخاص بـ JPasswordField والذي يقوم بإرجاع char [] وأسلوب getText () الذي تم إيقافه ويرجع كلمة المرور بنص واضح يوضح سبب الأمان. من الجيد اتباع نصيحة فريق جافا والالتزام بالمعيار بدلاً من اتباعه.


يمكن مسح صفائف الأحرف ( char[] ) بعد الاستخدام عن طريق تعيين كل حرف إلى صفر و سلاسل لا. إذا تمكن شخص ما من رؤية صورة الذاكرة بطريقة ما ، فيمكنه رؤية كلمة مرور بالنص العادي في حالة استخدام السلاسل ، ولكن إذا تم استخدام char[] ، بعد إزالة البيانات باستخدام 0 ، فستكون كلمة المرور آمنة.


ستكون الإجابة القصيرة والمباشرة لأنها char[]قابلة للتغيير بينما Stringلا تكون الكائنات.

Stringsفي جاوة هي كائنات غير قابلة للتغيير. لهذا السبب لا يمكن تعديلها بمجرد إنشائها ، وبالتالي فإن الطريقة الوحيدة لإزالة محتوياتها من الذاكرة هي جمعها. سيكون عندئذ فقط عندما يمكن الكتابة فوق الذاكرة المحررة بواسطة الكائن ، وستزول البيانات.

الآن لا يحدث تجميع البيانات المهملة في Java في أي فترة مضمونة. و Stringبالتالي يمكن أن تستمر في الذاكرة لفترة طويلة، وإذا تعطل عملية خلال هذا الوقت، ومحتويات السلسلة قد ينتهي في تفريغ الذاكرة أو بعض السجل.

باستخدام صفيف الحروف ، يمكنك قراءة كلمة المرور ، والانتهاء من العمل بها في أقرب وقت ممكن ، ثم تغيير المحتويات على الفور.


كما يقول Jon Skeet ، لا توجد طريقة إلا باستخدام التأمل.

ومع ذلك ، إذا كان الانعكاس خيارًا لك ، فيمكنك القيام بذلك.

public static void main(String[] args) {
    System.out.println("please enter a password");
    // don't actually do this, this is an example only.
    Scanner in = new Scanner(System.in);
    String password = in.nextLine();
    usePassword(password);

    clearString(password);

    System.out.println("password: '" + password + "'");
}

private static void usePassword(String password) {

}

private static void clearString(String password) {
    try {
        Field value = String.class.getDeclaredField("value");
        value.setAccessible(true);
        char[] chars = (char[]) value.get(password);
        Arrays.fill(chars, '*');
    } catch (Exception e) {
        throw new AssertionError(e);
    }
}

عند الجري

please enter a password
hello world
password: '***********'

ملاحظة: إذا تم نسخ char [] في السلسلة كجزء من دورة GC ، فهناك فرصة أن تكون النسخة السابقة في مكان ما في الذاكرة.

لن تظهر هذه النسخة القديمة في ملف تفريغ كومة الذاكرة المؤقتة ، ولكن إذا كان لديك وصول مباشر إلى الذاكرة الأولية للعملية ، يمكنك رؤيتها. بشكل عام يجب تجنب أي شخص لديه مثل هذا الوصول.


لا يوجد شيء يعطيك مجموعة char مقابل سلسلة String إلا إذا قمت بتنظيفها يدويًا بعد الاستخدام ، ولم أر أي شخص يقوم بذلك بالفعل. بالنسبة لي ، فإن تفضيل شار [] مقابل السلسلة هو مبالغ فيه قليلاً.

ألق نظرة على مكتبة Spring Security المستخدمة على نطاق واسع here واسأل نفسك - إن مستخدمي Spring Security غير كفؤين أو char [] كلمات المرور لا تعني الكثير. عندما يمسك قراصنة مقرفون ذاكرة الوصول العشوائي ذاكرة الوصول العشوائي الخاصة بك من ذاكرة الوصول العشوائي تأكد من أنها سوف تحصل على جميع كلمات المرور حتى لو كنت تستخدم طرق متطورة لإخفائها.

ومع ذلك ، تتغير Java طوال الوقت ، وقد تقوم بعض الميزات المرعبة مثل ميزة إلغاء نسخة Deduplication في Java 8 بالتدرب على كائنات String دون معرفتك. لكن هذا محادثة مختلفة.


بينما تبدو الاقتراحات الأخرى هنا صالحة ، هناك سبب وجيه آخر. مع String عادي لديك فرص أعلى بكثير من بطريق الخطأ طباعة كلمة المرور إلى سجلات أو شاشات أو بعض مكان آخر غير آمن. char[] أقل عرضة.

النظر في هذا:

public static void main(String[] args) {
    Object pw = "Password";
    System.out.println("String: " + pw);

    pw = "Password".toCharArray();
    System.out.println("Array: " + pw);
}

مطبوعات:

String: Password
Array: [[email protected]

تحرير: بالعودة إلى هذه الإجابة بعد عام من البحث الأمني ​​، أدركت أن هذا يعني أن ذلك سيؤدي في الواقع إلى مقارنة كلمات مرور النص العادي. من فضلك لا تفعل. استخدم تجزئة آمنة ذات اتجاه واحد مع الملح وعدد معقول من التكرار . فكر في استخدام مكتبة: هذه الأشياء يصعب الحصول عليها!

الجواب الأصلي: ماذا عن حقيقة أن String.equals () يستخدم تقييم دائرة قصر ، وبالتالي عرضة لهجوم توقيت؟ قد يكون من غير المحتمل ، ولكن يمكنك نظريا مقارنة كلمة المرور من أجل تحديد التسلسل الصحيح للأحرف.

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        // Quits here if Strings are different lengths.
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            // Quits here at first different character.
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

بعض الموارد أكثر على هجمات توقيت:


السلسلة في جافا غير قابلة للتغيير. لذلك عندما يتم إنشاء سلسلة ، سيبقى في الذاكرة حتى يتم تجميع البيانات المهملة. لذلك يمكن لأي شخص لديه حق الوصول إلى الذاكرة قراءة قيمة السلسلة.
إذا تم تعديل قيمة السلسلة ، فسيؤدي ذلك إلى إنشاء سلسلة جديدة. لذلك تبقى القيمة الأصلية والقيمة المعدلة في الذاكرة حتى يتم تجميع البيانات المهملة.

باستخدام مصفوفة الأحرف ، يمكن تعديل محتويات الصفيف أو محوها بمجرد تقديم الغرض من كلمة المرور. لن يتم العثور على محتويات المصفوفة الأصلية في الذاكرة بعد تعديلها وحتى قبل بدء تشغيل مجموعة جمع القمامة.

بسبب المخاوف الأمنية ، من الأفضل تخزين كلمة المرور كمصفوفة أحرف.


يعتقد بعض الناس أنه يجب عليك استبدال الذاكرة المستخدمة لتخزين كلمة المرور بمجرد عدم الحاجة إليها. يؤدي ذلك إلى تقليل الفترة الزمنية التي يحتاج فيها المهاجم إلى قراءة كلمة المرور من النظام ويتجاهل تمامًا حقيقة أن المهاجم يحتاج بالفعل إلى وصول كافٍ لاختطاف ذاكرة JVM للقيام بذلك. يمكن للمهاجم مع هذا العدد الكبير من الوصول إلى الأحداث الرئيسية الخاصة بك جعل هذا عديم الفائدة تماما (AFAIK ، لذا يرجى تصحيح لي إذا كنت مخطئا).

تحديث

بفضل التعليقات يجب أن أقوم بتحديث جوابي. ويبدو أن هناك حالتين يمكن أن يضيف فيها ذلك تحسينًا أمنيًا طفيفًا (للغاية) لأنه يقلل من الوقت الذي قد تهبط فيه كلمة المرور على القرص الصلب. ما زلت أعتقد أنه مبالغة بالنسبة لمعظم حالات الاستخدام.

  • قد يكون نظامك المستهدف قد تمت تهيئته بشكل سيئ أو عليك أن تفترض أنه يجب عليك أن تكون بجنون العظمة حول عمليات التفريغ الأساسية (يمكن أن تكون صالحة إذا لم يتم إدارة الأنظمة من قبل المسؤول).
  • يجب أن يكون برنامجك مصابًا بجنون العظمة بشكل مفرط لمنع تسرب البيانات مع حصول المهاجم على إمكانية الوصول إلى الأجهزة - باستخدام أشياء مثل TrueCrypt (تم إيقافها) أو CipherShed أو CipherShed .

إذا كان ذلك ممكناً ، سيؤدي تعطيل عمليات تفريغ الأساسية وملف المبادلة إلى معالجة كلا المشكلتين. ومع ذلك ، فإنها تتطلب حقوق المسؤول ويمكن أن تقلل الوظائف (ذاكرة أقل لاستخدام) وسحب ذاكرة الوصول العشوائي من نظام تشغيل سيظل مصدر قلق صحيح.





char