مدتی پیش این پست در وبسایت 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))
هیستوگرام طول پسوردها شاید نمودار گویاتری نسبت به نمودار جعبهای آن باشد:
> library(ggplot2) > qplot(nchar(passwords) , main = "password length" , xlab = "length") + geom_histogram(bins = 35)
معمولا گفته میشود که یک پسورد قوی باید حداقل ۸ کاراکتر شامل یک حرف بزرگ، یک حرف کوچک، یک عدد و یک کاراکتر ويژه( @#!$ … ) باشد، یک چیزی در این مایهها:
نکته بیربط به این پست: بعضیها میگویند پسورد را باید به این صورت بسازیم:
برای این که بفهمیم چه بخشی از این پسوردها قوی هستند، باید تعداد کاراکترها، تعداد حروف/ حروف بزرگ، اعداد و کاراکترهای ویژهی انها را بشماریم. بهترین ابزار برای این کار، استفاده از 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 "))
همانطور که میبینید، نمودار 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")
در مرحلهی بعدی میخواهیم روی ترکیب کاراکترهای گذرواژهها با طولهای مشخص، تحلیلی انجام دهیم، درصد کاراکترهای تشکیل دهندهی گذرواژهها را از مراحل قبلی داریم، برای این تحلیل میتوانیم یک تابع به صورت زیر بنویسیم:
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)
نموداری که ساختیم، در یک نگاه نشان میدهد که کسانی که از گذرواژههایی با طول بین ۵ تا ۱۳ استفاده میکنن تمایل چندانی به استفاده از علائم خاص در گذرواژه ندارند. در قسمت بعدی این پست، سعی میکنیم با استفاده از فرمولهایی که توسط سایتهای بررسی قدرت پسورد ارایه شده، خودمان قدرت این پسوردها را محاسبه کنیم.