Blowfish ile uzun şifreleri (> 72 karakter) hash etme


91

Geçen hafta şifre karma hakkında birçok makale okudum ve Blowfish şu anda en iyi karma algoritma (biri) gibi görünüyor - ama bu sorunun konusu bu değil!

72 karakter sınırı

Blowfish, girilen şifrede yalnızca ilk 72 karakteri dikkate alır:

<?php
$password = "Wow. This is a super secret and super, super long password. Let's add some special ch4r4ct3rs a#d everything is fine :)";
$hash = password_hash($password, PASSWORD_BCRYPT);
var_dump($password);

$input = substr($password, 0, 72);
var_dump($input);

var_dump(password_verify($input, $hash));
?>

Çıktı:

string(119) "Wow. This is a super secret and super, super long password. Let's add some special ch4r4ct3rs a#d everything is fine :)"
string(72) "Wow. This is a super secret and super, super long password. Let's add so"
bool(true)

Gördüğünüz gibi sadece ilk 72 karakter önemli. Twitter, şifrelerini ( https://shouldichangemypassword.com/twitter-hacked.php ) saklamak için blowfish aka bcrypt kullanıyor ve ne olduğunu tahmin edin : Twitter şifrenizi 72 karakterden daha uzun bir şifre ile değiştirin ve hesabınıza şu şekilde giriş yapabilirsiniz: yalnızca ilk 72 karakteri girerek.

Balon Balığı ve Biber

Şifreleri "karalayan" birçok farklı görüş var. Bazı insanlar bunun gereksiz olduğunu söyler, çünkü gizli biber dizisinin de bilindiğini / yayınlandığını varsaymanız gerekir, böylece hash'i geliştirmez. Ayrı bir veritabanı sunucum var, bu nedenle yalnızca veritabanının sızdırılmış olması ve sabit biberin olmaması oldukça olası.

Bu durumda (biber sızdırmaz) bir sözlüğe dayalı bir saldırıyı daha zor hale getirirsiniz (bu doğru değilse düzeltin). Biber ipiniz de sızdırılmışsa: o kadar da kötü değil - hala tuzunuz var ve bibersiz bir esrar kadar iyi korunuyor.

Bu yüzden şifreyi karıştırmanın en azından kötü bir seçim olmadığını düşünüyorum.

Öneri

72 karakterden (ve biberden) daha uzun bir şifre için Blowfish karması alma önerim şudur:

<?php
$pepper = "foIwUVmkKGrGucNJMOkxkvcQ79iPNzP5OKlbIdGPCMTjJcDYnR";

// Generate Hash
$password = "Wow. This is a super secret and super, super long password. Let's add some special ch4r4ct3rs a#d everything is fine :)";
$password_peppered = hash_hmac('sha256', $password, $pepper);
$hash = password_hash($password_peppered, PASSWORD_BCRYPT);

// Check
$input = substr($password, 0, 72);
$input_peppered = hash_hmac('sha256', $input, $pepper);

var_dump(password_verify($input_peppered, $hash));
?>

Bu, şu soruya dayanmaktadır : password_verifydönüş false.

Soru

Daha güvenli yol nedir? Önce bir SHA-256 karma (64 karakter döndürür) almak mı yoksa parolanın yalnızca ilk 72 karakterini mi düşünüyorsunuz?

Artıları

  • Kullanıcı sadece ilk 72 karakteri girerek oturum açamaz
  • Karakter sınırını aşmadan biber ekleyebilirsiniz.
  • Hash_hmac'ın çıktısı muhtemelen parolanın kendisinden daha fazla entropiye sahip olacaktır.
  • Parola, iki farklı işlev tarafından karma hale getirilir

Eksileri

  • Balon balığı hashini oluşturmak için yalnızca 64 karakter kullanılır


Düzenleme 1: Bu soru sadece blowfish / bcrypt'in PHP entegrasyonunu ele almaktadır. Yorumlar için teşekkürler!


3
Parolayı kısaltan tek kişi Blowfish değil, insanları gerçekte olduğundan daha güvenli olduğunu düşünmeye yönlendiriyor. İşte 8 karakterlik sınırın ilginç bir geçmişi.
DOK

2
72 karakterlik kesme Blowfish algoritması için mi yoksa sadece PHP uygulaması için mi temeldir? IIRC Blowfish ayrıca kullanıcı parolalarını şifrelemek için (en azından bazılarında) nixlerde kullanılır.
Douglas B. Staple

3
Sorun Blowfish değil, Bcrypt ile ilgilidir. Bu sorunu yalnızca Python ve Bcrypt ile yeniden oluşturabilirim.
Blender

@Blender: Yorumunuz ve üzerinde yaptığınız çalışmalar için teşekkür ederiz. Blowfish ve bcrypt için php'de farklı işlevler bulamadım ve aynı olsalar da. Ama php'de benim için bir fark yaratmıyor mu? Standart php işlevini kullanmayı tercih ederim.
Frederik Kammer

1
Ayrıca Openwall'un PHP şifre karma çerçevesine (PHPass) bakın. Taşınabilir ve kullanıcı şifrelerine yönelik bir dizi yaygın saldırıya karşı güçlendirilmiştir. Çerçeveyi yazan adam (SolarDesigner), John The Ripper'ı yazan ve Password Hashing Yarışmasında jüri olarak oturan aynı adam . Yani şifrelere yapılan saldırılar hakkında bir iki şey biliyor.
jww

Yanıtlar:


137

Buradaki sorun temelde bir entropi sorunudur. Öyleyse oraya bakmaya başlayalım:

Karakter Başına Entropi

Bayt başına entropi bit sayısı:

  • Onaltılık Karakterler
    • Bit sayısı: 4
    • Değerler: 16
    • 72 Karakterde Entropi: 288 bit
  • Alfa Sayısal
    • Bit Sayısı: 6
    • Değerler: 62
    • 72 Karakterde Entropi: 432 bit
  • "Ortak" Semboller
    • Bitler: 6.5
    • Değerler: 94
    • 72 Karakterde Entropi: 468 bit
  • Tam Bayt
    • Bitler: 8
    • Değerler: 255
    • 72 Karakterde Entropi: 576 bit

Öyleyse, nasıl davranacağımız, ne tür karakterler beklediğimize bağlıdır.

İlk Sorun

Kodunuzla ilgili ilk sorun, "biber" karma adımınızın onaltılık karakterler çıkarmasıdır (dördüncü parametrehash_hmac() ayarlanmadığından).

Bu nedenle, biberinizi karıştırarak, şifrenin kullanabileceği maksimum entropiyi etkili bir şekilde 2 kat azaltıyorsunuz ( olası 576'dan 288'e bit) .

İkinci Problem

Ancak, ilk etapta sha256yalnızca 256entropi bitleri sağlar . Yani 576 biti 256 bit'e kadar etkili bir şekilde kesiyorsunuz. Karma adımınız * hemen *, tanımı gereği şifredeki olası entropinin en az % 50'sini kaybeder .

SHA512Mevcut entropiyi yalnızca yaklaşık% 12 azaltacağınız yere geçerek bunu kısmen çözebilirsiniz . Ancak bu yine de önemsiz olmayan bir fark. Bu% 12, permütasyon sayısını bir faktör kadar azaltır 1.8e19. Bu büyük bir sayı ... Ve bu onu azalttığı faktör ...

Temel Sorun

Temel sorun, 72 karakterden uzun üç tür parola olmasıdır. Bu tarz sistemin onlar üzerindeki etkisi çok farklı olacaktır:

Not: Bundan sonra SHA512, ham çıktıyla (hex değil) kullanan bir biber sistemiyle karşılaştırdığımızı varsayıyorum .

  • Yüksek entropili rastgele şifreler

    Bunlar, parolalar için ne kadar büyük anahtarlar üreten parola oluşturucuları kullanan kullanıcılarınızdır. Rastgele (oluşturulmuş, insan tarafından seçilmemiş) ve karakter başına yüksek entropiye sahip. Bu türler yüksek bayt (karakterler> 127) ve bazı kontrol karakterleri kullanır.

    Bu grup için, hashing fonksiyonunuz mevcut entropiyi önemli ölçüde azaltacaktır bcrypt.

    Tekrar söyleyeyim. Yüksek entropi, uzun parolalar kullanan kullanıcılar için çözümünüz , parolalarının gücünü ölçülebilir bir miktarda önemli ölçüde azaltır. (72 karakterlik bir parola için 62 bitlik kayıp ve daha uzun parolalar için daha fazlası)

  • Orta entropi rasgele şifreler

    Bu grup, ortak semboller içeren şifreler kullanıyor, ancak yüksek bayt veya kontrol karakterleri kullanmıyor. Bunlar yazılabilir şifrelerinizdir.

    Bu grup için, biraz daha fazla entropinin kilidini açacaksınız (onu yaratmayacak, ancak daha fazla entropinin bcrypt şifresine sığmasına izin vereceksiniz). Biraz dediğimde, biraz kast ediyorum. Başa dön, SHA512'nin sahip olduğu 512 biti maksimize ettiğinizde ortaya çıkar. Bu nedenle, zirve 78 karakterde.

    Tekrar söyleyeyim. Bu sınıftaki parolalar için, entropi bitmeden önce yalnızca ek 6 karakter kaydedebilirsiniz.

  • Düşük entropili rastgele olmayan şifreler

    Bu, muhtemelen rastgele oluşturulmayan alfa sayısal karakterleri kullanan gruptur. İncil'den alıntı falan gibi bir şey. Bu ifadeler karakter başına yaklaşık 2,3 bit entropiye sahiptir.

    Bu grup için, karma oluşturma yoluyla daha fazla entropinin kilidini önemli ölçüde açabilirsiniz (onu oluşturmaz, ancak bcrypt şifre girişine daha fazla sığdırabilirsiniz). Başabaş, entropi bitmeden yaklaşık 223 karakterdir.

    Tekrar söyleyelim. Bu sınıftaki parolalar için, ön hashing kesinlikle güvenliği önemli ölçüde artırır.

Gerçek Dünyaya Dönüş

Bu tür entropi hesaplamaları gerçek dünyada pek önemli değil. Önemli olan entropiyi tahmin etmektir. Saldırganların yapabileceklerini doğrudan etkileyen şey budur. En üst düzeye çıkarmak istediğiniz şey budur.

Entropiyi tahmin etmeye yönelik çok az araştırma olsa da, belirtmek istediğim bazı noktalar var.

Arka arkaya 72 doğru karakteri rastgele tahmin etme şansı son derece düşüktür. Powerball piyangosunu 21 kez kazanma olasılığınız, bu çarpışmaya sahip olmaktansa ... İşte bu kadar büyük bir sayıdan bahsediyoruz.

Ama istatistiksel olarak yanılmayabiliriz. Cümleler söz konusu olduğunda, ilk 72 karakterin aynı olma şansı, rastgele bir paroladan çok daha yüksektir. Ancak yine de önemsiz derecede düşük (karakter başına 2,3 bit temel alınarak Powerball piyangosunu 5 kez kazanma olasılığınız daha yüksektir).

Pratikte

Pratik olarak, gerçekten önemli değil. Birinin ilk 72 karakteri doğru tahmin etme şansı, sonrakilerin önemli bir fark yaratması o kadar düşük ki endişelenmeye değmez. Neden?

Peki, bir cümle aldığınızı varsayalım. Kişi ilk 72 karakteri doğru anlayabiliyorsa, ya gerçekten şanslı (muhtemel değil) ya da bu yaygın bir ifade. Yaygın bir ifadeyse, tek değişken ne kadar uzun yapılacağıdır.

Bir örnek alalım. İncil'den bir alıntı yapalım (başka bir nedenle değil, uzun metnin ortak bir kaynağı olduğu için):

Komşunun evine göz dikmeyeceksin. Komşunuzun karısına, uşağına ya da hizmetçisine, öküzüne ya da eşeğine ya da komşunuza ait herhangi bir şeye göz dikmeyeceksiniz.

Bu 180 karakter. 73. karakter gikinci karakterdir neighbor's. Bu kadar çok tahmin ettiyseniz, muhtemelen durmayacaksınız nei, ancak ayetin geri kalanına devam ediyorsunuz (çünkü şifre büyük olasılıkla bu şekilde kullanılacaktır). Bu nedenle, "hash" iniz pek bir şey eklemedi.

BTW: KESİNLİKLE bir incilden alıntı kullanmayı savunmuyorum. Aslında tam tersi.

Sonuç

İlk önce hashing yaparak uzun şifreleri kullanan insanlara pek yardımcı olmayacaksınız. Kesinlikle yardımcı olabileceğiniz bazı gruplar. Bazılarını kesinlikle incitebilirsin.

Ama sonuçta bunların hiçbiri aşırı derecede önemli değil. Biz ilgileniyor sayılar sadece vardır YOL çok yüksek. Entropideki fark çok fazla olmayacak.

Bcrypt'i olduğu gibi bırakmanız daha iyi. Karıştırmayı bozma olasılığınız (kelimenin tam anlamıyla, zaten yaptınız ve bu hatayı yapan ilk veya son değilsiniz), engellemeye çalıştığınız saldırı gerçekleşecek.

Sitenin geri kalanını korumaya odaklanın. Ve parola gücünü belirtmek için kayıt sırasında parola kutusuna bir parola entropi ölçer ekleyin (ve bir parolanın kullanıcının değiştirmek isteyebileceği kadar uzun olup olmadığını belirtin) ...

Bu benim en az 0,02 dolarım (veya muhtemelen 0,02 dolardan çok daha fazla) ...

"Gizli" Bir Biber Kullanırken:

Bir hash işlevini bcrypt'e beslemek için tam anlamıyla hiçbir araştırma yoktur. Bu nedenle, "biberli" bir hash'i bcrypt'e beslemenin, bilinmeyen güvenlik açıklarına neden olup olmayacağı en iyi ihtimalle belirsizdir (yapmanın hash1(hash2($value)), çarpışma direnci ve ön görüntü saldırıları etrafındaki önemli güvenlik açıklarını ortaya çıkarabileceğini biliyoruz ).

Zaten gizli bir anahtarı ("biber") saklamayı düşündüğünüzü düşünürsek, neden onu iyi çalışılmış ve anlaşılmış bir şekilde kullanmıyorsunuz? Neden karmayı depolamadan önce şifrelemiyorsunuz?

Temel olarak, parolayı karıştırdıktan sonra, tüm karma çıktısını güçlü bir şifreleme algoritmasına besleyin. Ardından şifrelenmiş sonucu saklayın.

Şimdi, bir SQL-Enjeksiyon saldırısı, şifreleme anahtarına sahip olmadıkları için yararlı hiçbir şey sızdırmaz. Ve eğer anahtar sızdırılırsa, saldırganların durumu, sade bir hash kullandığınızdan daha iyi olamaz (ki bu kanıtlanabilir, "ön karma" biberinin sağlamadığı bir şey).

Not: Bunu yapmayı seçerseniz, bir kitaplık kullanın. PHP için Zend Framework 2'leri şiddetle tavsiye ederimZend\Crypt paketini . Aslında şu anda bu noktada önerdiğim tek şey bu. İyice gözden geçirildi ve sizin için tüm kararları veriyor (ki bu çok iyi bir şey) ...

Gibi bir şey:

use Zend\Crypt\BlockCipher;

public function createHash($password) {
    $hash = password_hash($password, PASSWORD_BCRYPT, ["cost"=>$this->cost]);

    $blockCipher = BlockCipher::factory('mcrypt', array('algo' => 'aes'));
    $blockCipher->setKey($this->key);
    return $blockCipher->encrypt($hash);
}

public function verifyHash($password, $hash) {
    $blockCipher = BlockCipher::factory('mcrypt', array('algo' => 'aes'));
    $blockCipher->setKey($this->key);
    $hash = $blockCipher->decrypt($hash);

    return password_verify($password, $hash);
}

Ve faydalıdır çünkü tüm algoritmaları iyi anlaşılan ve iyi çalışılan şekillerde (en azından nispeten) kullanıyorsunuz. Hatırlamak:

En beceriksiz amatörden en iyi kriptografa kadar herkes kendi kıramayacağı bir algoritma yaratabilir.


6
Bu detaylı cevap için çok çok teşekkür ederim. Bu bana gerçekten yardımcı oluyor!
Frederik Kammer

1
Bu cevaba iltifat ediyorum. Yine de küçük bir ayrıntı, kullanıcıların büyük çoğunluğu, parolaları kırmak için bir sözlükte bulunan çok zayıf parolalar, kelimeler ve türevler kullanır, bir biber onları entrofi sorularından bağımsız olarak korur. Entrofiyi kaybetmemek için şifre ve biberi birleştirebilirsiniz. Bununla birlikte, karma değeri şifrelemeyle ilgili öneriniz, muhtemelen sunucu tarafı sırrı eklemek için en iyi çözümdür.
martinstoeckli

2
@martinstoeckli: Biber kavramı ile ilgili sorunum onun değerinde değil. "Biber" uygulamasının, kriptografik algoritmalar açısından keşfedilmemiş bölgeye girmesi. Bu iyi bir şey değil. Bunun yerine, kriptografik ilkelerin, birlikte çalışacak şekilde tasarlanacak şekilde birleştirilmesi gerektiğine inanıyorum. Temel olarak, kriptografi hakkında hiçbir şey bilmeyen bazılarının "Daha fazla karma daha iyidir! Tuzumuz var, biber de iyidir!" . Daha basit, daha test edilmiş ve daha yalın bir uygulamaya sahip olmayı tercih ederim
ircmaxell

@ircmaxell - Evet, bakış açınızı biliyorum ve karma değerler daha sonra şifreleneceği sürece katılıyorum. Bu ek adımı atmazsanız, bir sözlük saldırısı, iyi bir karma algoritma ile bile çok fazla zayıf parolayı ortaya çıkaracaktır.
martinstoeckli

1
@martinstoeckli: Orada katılmıyorum. Sır saklamak, yapılacak önemsiz bir şey değildir. Bunun yerine, bcrypt'i iyi bir maliyetle kullanırsanız (bugün 12), en zayıf sınıftaki parolalar dışında tümü güvenlidir (sözlük ve önemsiz parolalar zayıf olanlardır). Bu yüzden insanlara, kullanıcıyı güç ölçerlerle eğitmeye ve ilk etapta daha iyi şifreler kullanmalarını sağlamaya odaklanmalarını tavsiye ederim ...
ircmaxell

5

Şifreleri karıştırmak kesinlikle iyi bir şey, ama nedenini görelim.

Öncelikle soruyu tam olarak bir biber yardımcı olduğunda cevaplamalıyız. Biber yalnızca gizli kaldığı sürece şifreleri korur, bu nedenle bir saldırganın sunucunun kendisine erişimi varsa, hiçbir faydası yoktur. Çok daha kolay bir saldırı, veritabanına okuma erişimine izin veren SQL enjeksiyonudur (hash değerlerimiz için), ne kadar kolay olabileceğini göstermek için bir SQL enjeksiyon demosu hazırladım (hazırlanmak için bir sonraki oka tıklayın) giriş).

O halde biber aslında neye yardımcı oluyor? Biber gizli kaldığı sürece zayıf şifreleri sözlük saldırılarından korur. Parola 1234daha sonra şöyle bir şey olur 1234-p*deDIUZeRweretWy+.O. Bu şifre sadece çok uzun değil, aynı zamanda özel karakterler de içeriyor ve hiçbir zaman herhangi bir sözlüğün parçası olmayacak.

Artık kullanıcılarımızın hangi şifreleri kullanacağını tahmin edebiliriz, 64-72 karakter arasında şifreleri olan kullanıcılar olduğu için muhtemelen daha fazla kullanıcı zayıf şifreler girecektir (aslında bu çok nadir olacaktır).

Bir başka nokta da kaba zorlama aralığıdır. Sha256 hash işlevi, 256 bit çıktı veya 1.2E77 kombinasyonu döndürecektir, bu, GPU'lar için bile kaba zorlama için çok fazla yol (doğru hesapladıysam, bunun 2013'te bir GPU'da yaklaşık 2E61 yıla ihtiyacı olacaktır ). Yani biberi uygulamada gerçek bir dezavantaj yaşamıyoruz. Karma değerler sistematik olmadığından, yaygın kalıplarla kaba zorlamayı hızlandıramazsınız.

Not: Bildiğim kadarıyla 72 karakter sınırı BCrypt'in algoritmasına özeldir. Bulduğum en iyi cevap bu .

PPS Örneğinizin kusurlu olduğunu düşünüyorum, tam şifre uzunluğunda bir karma oluşturamaz ve kesilmiş bir şifre ile doğrulayamazsınız. Muhtemelen biberi, esrar üretmek ve esrarın doğrulanması için aynı şekilde uygulamayı düşündünüz.


PPS'nizle ilgili olarak şunu söyleyebilirim: Evet, kesilmiş şifreyi kesilmemiş olanın karması ile doğrulayabilir ve yine de alabilir true. Bu soru tamamen bununla ilgili. Kendinize bir göz atın: viper-7.com/RLKFnB
Sliq

@Panique - Sorun BCrypt hash'inin hesaplanması değil, daha önceki HMAC. SHA hash oluşturmak için OP, tam uzunlukta parolayı kullanır ve sonucu BCrypt için girdi olarak kullanır. Doğrulama için, SHA karmasını hesaplamadan önce şifreyi keser, ardından bu tamamen farklı sonucu BCrypt için girdi olarak kullanır. HMAC, herhangi bir uzunluktaki girişi kabul eder.
martinstoeckli

2

Bcrypt, pahalı Blowfish anahtar kurulum algoritmasına dayalı bir algoritma kullanır.

Bcrypt için önerilen 56 bayt parola sınırı (boş sonlandırma baytı dahil), Blowfish anahtarının 448 bit sınırıyla ilgilidir. Bu sınırı aşan herhangi bir bayt, elde edilen hash'e tam olarak karıştırılmaz. Bcrypt şifreleri üzerindeki 72 baytlık mutlak sınır, bu baytların sonuçta ortaya çıkan karma üzerindeki gerçek etkisini düşündüğünüzde daha az alakalı olacaktır.

Kullanıcılarınızın normalde 55 baytı aşan şifreleri seçeceğini düşünüyorsanız, şifre tablosu ihlali durumunda güvenliği artırmak için her zaman şifre genişletme turlarını artırabileceğinizi unutmayın (ancak bu, fazladan eklemekle karşılaştırıldığında çok daha fazla olmalıdır. karakterler). Kullanıcıların erişim hakları, kullanıcıların normalde çok uzun bir parola gerektirecek kadar önemliyse, parolanın sona erme süresi de 2 hafta gibi kısa olmalıdır. Bu, bir bilgisayar korsanı, her bir deneme şifresinin eşleşen bir karma oluşturup oluşturmayacağını görmek için her bir deneme şifresini test etmede yer alan çalışma faktörünü yenmek için kaynaklarını yatırırken, şifrenin geçerli kalma olasılığının çok daha düşük olduğu anlamına gelir.

Elbette, şifre tablosunun ihlal edilmemesi durumunda, kullanıcının hesabını kilitlemeden önce, bir kullanıcının 55 baytlık şifresini tahmin etmek için en fazla on kez bilgisayar korsanlarına izin vermeliyiz;

55 bayttan daha uzun bir parolayı önceden karma haline getirmeye karar verirseniz, sınırı aşmadan en büyük çıkışa sahip olduğundan SHA-384'ü kullanmalısınız.


1
"parola son kullanma tarihi de 2 hafta" gibi kısa olmalıdır, "çok uzun parolalar", gerçekten, neden parolayı kaydetmeye bile zahmet edesiniz ki, her seferinde parola sıfırlamayı kullanın. Cidden, bu yanlış çözüm, bir belirteçle iki faktörlü kimlik doğrulamaya geçin.
zaph

Teşekkürler @zaph. Bana bunun bir örneğini gösterebilir misin? Kulağa ilginç geliyor.
Phil

[DRAFT NIST Özel Yayını 800-63B Dijital Kimlik Doğrulama Kılavuzu] ( pages.nist.gov/800-63-3/sp800-63b.html ), 5.1.1.2. Hafızaya Alınmış Gizli Doğrulayıcılar: Doğrulayıcılar, hafızaya alınmış sırların keyfi olarak (örneğin, periyodik olarak) değiştirilmesini GEREKTİRMEMELİDİR . Ayrıca Jim Fenton tarafından yazılan Daha İyi Parola Gereksinimlerine Doğru bölümüne bakın .
zaph

1
Mesele şu ki, bir kullanıcının bir parolayı ne kadar sık ​​değiştirmesi gerekiyorsa, parola seçenekleri de o kadar kötüleşir ve böylece güvenliği azaltır. Kullanıcıların sınırlı miktarda ezberlenebilir şifreleri var ve ya gerçekten kötü şifreler seçiyorlar ya da klavyenin altına
yapıştırılmış
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.