منبع اصلی نوشتار زیر در این لینک قرار دارد

تحلیل ۲ میلیون گذرواژه در R، بخش اول

مدتی پیش این پست در وبسایت data science central نظرم را جلب کرد، نویسنده، لینکی به یک فایل متنی ۲۰ مگابایتی که حاوی حدود ۲ میلیون گذرواژه بدست آمده از ایمیلهای به سرقت رفته است را به اشتراک گذاشته و از بقیه خواسته تا با مهارتهایشان این فایل را تحلیل کنند. تحلیل کاربری به نام Jianhua Li  از این فایل در گیتهاب نظر مرا به خود جلب کرد و از آنجایی که این تحلیل در پایتون( پیتون؟) انجام شده بود، تصمیم گرفتم آن را در R انجام دهم.

بعد از دانلود فایل، آن را در working directory قرار می‌دهیم. فایل متنی به صورتی است که در هر خط آن یک پسورد نوشته شده، برای همین دستوری لازم داریم که آن را خط به خط بخواند و در ‌‌R وارد کند:

passwords <- readLines("passwords.txt")

(این نکته را در نظر داشته باشید که اول فایل متنی چند خط کامنت وجود دارد که باید پاک شود. بعد از خواندن فایل سر و ته آن را سرکی می‌کشیم:

> head(passwords,5)
[1] "!"          "! love you" "!!"         "!!!"        "!!!!!"     
> tail(passwords,5)
[1] "~~sstux"      "~~zhou075278" "~~~~"         "~~~~~"        "~~~~~~"

در فایل دقیقا چند پسورد وجود داشته؟ با یک دستور ساده جواب را به دست می‌آوریم:

> length(passwords)
[1] 2151220

دستور nchar تعداد کاراکترهای یک string را در R تحویل می‌دهد( space هم در این دستور یک کاراکتر محسوب می‌شود)، با ترکیب این دستور و دستور summary ، می‌توانیم ببینیم که متوسط طول پسوردها در چه محدوده‌ای است و مینیمون و ماکسیمم و میانگین آن را بدست آوریم:

> summary(nchar(passwords))
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   1.00    7.00    8.00    8.37   10.00   29.00

رسم نمودار box plot طول گذرواژه‌ها، نشان می‌دهد که بیشتر آنها بین ۸ تا ۱۰ کاراکتری هستند:

> boxplot(nchar(passwords))

rplot01

هیستوگرام طول پسوردها شاید نمودار گویاتری نسبت به نمودار جعبه‌ای آن باشد:

> library(ggplot2)
> qplot(nchar(passwords) , main = "password length" , xlab = "length") + geom_histogram(bins = 35)

rplot02

 

معمولا گفته می‌شود که یک پسورد قوی باید حداقل ۸ کاراکتر شامل یک حرف بزرگ، یک حرف کوچک، یک عدد و یک کاراکتر ويژه( @#!$ … ) باشد، یک چیزی در این مایه‌ها:

password-requirements

نکته بی‌ربط به این پست: بعضی‌ها می‌گویند پسورد را باید به این صورت بسازیم:

password_strength

برای این که بفهمیم چه بخشی از این پسوردها قوی هستند، باید تعداد کاراکترها، تعداد حروف/ حروف بزرگ، اعداد و کاراکترهای ویژه‌ی انها را بشماریم. بهترین ابزار برای این کار، استفاده از regex و تابع grep است. اگر نمی‌دانید regex چیست، این ویدیوی جادی را ببینید. برای این که ببینیم چه درصدی از پسوردها شامل عدد هستند، کافی است تعدادشان را بشماریم، به تعداد کل پسوردها تقسیم کنیم و در صد ضرب کنیم، به همین راحتی:

> (length(grep("[[:digit:]]" , passwords)) / length(passwords)) * 100

برای نشان دادن این داده در یک نمودار pie می‌توانیم از دستور زیر استفاد کنیم:

> pie( c(length(grep("[[:digit:]]" , passwords)), (length(passwords) - length(grep("[[:digit:]]" , passwords)))) , col= c( "blue", "green") , labels = c("with numbers" , "with out numbers "))

rplot

همانطور که می‌بینید، نمودار pie، چندان نمودار گویا و بدرد بخوری نیست و به طور کلی توصیه می‌شود که از آن استفاده نکنید. بهتر است ترکیب هر پسورد را در قالب ستون‌هایی به داده اضافه کنیم:

pass <- passwords
 pass <- cbind(pass, grepl("[[:alpha:]]",pass))
 pass <- cbind(pass , grepl("[[:upper:]]",pass[,1]))
 pass <- cbind(pass , grepl("[[:digit:]]",pass[,1]))
 pass <- cbind(pass , grepl("[[:punct:]]",pass[,1]))
 pass <- as.data.frame(pass)
 #function to determine composition of passwords
 compo <- function(x) {
   n <- nchar(x)
   x <- strsplit(x , split = "")
   x <- x[[1]]
   letterpct <- (length(grep("[[:alpha:]]",x)) / n) * 100
   digitpct  <- (length(grep("[[:digit:]]",x)) / n) * 100
   punnctpct <- ((length(grep("[[:punct:]]",x)) + length(grep("[[:space:]]",x)))/ n) * 100
   result    <- c(letterpct , punnctpct ,digitpct )
   result
 }

 m <-  t(sapply(passwords, compo))
 m <- as.data.frame(m)
 pass <- cbind(pass , m)
 colnames(pass) <- c("passwords" ,"has letter" , "has capital letter" , "has digit" , "has symbol", "pct of letters" , "pct of symbols", "pct of numbers" )
length <- nchar(passwords)
pass <- cbind(pass , length)

حاصل این کدها یک دیتافریم به شکل زیر است:

> head(pass)
   passwords has letter has capital letter has digit has symbol pct of letters pct of symbols pct of numbers
1          !      FALSE              FALSE     FALSE       TRUE              0            100              0
2 ! love you       TRUE              FALSE     FALSE       TRUE             70             30              0
3         !!      FALSE              FALSE     FALSE       TRUE              0            100              0
4        !!!      FALSE              FALSE     FALSE       TRUE              0            100              0
5      !!!!!      FALSE              FALSE     FALSE       TRUE              0            100              0
6     !!!!!!      FALSE              FALSE     FALSE       TRUE              0            100              0

هم‌اکنون به راحتی می‌توانیم درصد‌هایی که نیار داشتیم را محاسبه کنیم:

capletter <-  sum(pass$`has capital letter`==TRUE) / length(passwords)  * 100
 letter   <-  sum(pass$`has letter`==TRUE) / length(passwords)  * 100
 digit    <-  sum(pass$`has digit`==TRUE) / length(passwords)  * 100
 symbol   <-  sum(pass$`has symbol`==TRUE) / length(passwords)  * 100
letteronly   <-  sum(pass$`has letter`==TRUE & pass$`has digit`==FALSE & pass$`has symbol`==FALSE) / length(passwords)  * 100
digitsonly   <-  sum(pass$`has letter`==FALSE & pass$`has digit`==TRUE & pass$`has symbol`==FALSE) / length(passwords)  * 100
symbolonly   <-  sum(pass$`has letter`==FALSE & pass$`has digit`==FALSE & pass$`has symbol`==TRUE) / length(passwords)  * 100
allThree  <-  sum(pass$`has letter`==TRUE & pass$`has digit`==TRUE & pass$`has symbol`==TRUE) / length(passwords)  * 100

تمامی این درصدها را در قالب یک نمودار می‌توان نشان داد:

df <- cbind(capletter,letter,digit,symbol,letteronly,digitonly,symbolonly,allThree)
colnames(df) <- cbind("has uppercase letter","has letter","has digit","has symbol" ,"letter only","digit only","symbol only","all Three")
df <- rbind(df , 100 - df)
bp <- barplot(df , col=c("black","grey") , legend = c("yes" , "no"))
text(bp, 0, round(df[1,], 2),cex=1,pos=3 ,col = "white")

rplot02در مرحله‌ی بعدی می‌خواهیم روی ترکیب کاراکترهای گذرواژه‌ها با طولهای مشخص، تحلیلی انجام دهیم، درصد کاراکترهای تشکیل دهنده‌ی گذرواژه‌ها را از مراحل قبلی داریم، برای این تحلیل می‌توانیم یک تابع به صورت زیر بنویسیم:

pctcompo <- function(n) {
  numbers <- mean(pass$`pct of numbers`[pass$length == n])
  letters <- mean(pass$`pct of letters`[pass$length == n])
  symbols <- mean(pass$`pct of symbols`[pass$length == n])
  c <- cbind(numbers,letters,symbols)
  c
}

با استفاده از این تابع تحلیل مورد نظر به راحتی انجام می‌شود:

m <- t(sapply(1:29,pctcompo))
colnames(m) <- c("numbers" , "digits", "symbols")
m <- as.data.frame(m)

با کمک پکیج‌های reshape2 و ggplot2 یک نمودار بسیار زیبا از تحلیل ترکیب گذرواژه‌ها با طولهای مختلف می‌سازیم:

library(reshape2)
library(ggplot2)
mm <- melt(m , id.vars = "length")
ggplot(mm, aes(x = length, y = value,fill=variable)) + geom_bar(stat='identity') +scale_x_continuous(breaks = 1:29)

rplot03

نموداری که ساختیم، در یک نگاه نشان می‌دهد که کسانی که از گذرواژه‌هایی با طول بین ۵ تا ۱۳ استفاده می‌کنن تمایل چندانی به استفاده از علائم خاص در گذرواژه ندارند. در قسمت بعدی این پست، سعی می‌کنیم با استفاده از فرمولهایی که توسط سایتهای بررسی قدرت پسورد ارایه شده، خودمان قدرت این پسوردها را محاسبه کنیم.