Иногда возникает необходимость передать какие-либо данные из приложения ASP.NET в JavaScript таким образом, чтобы пользователь не узнал, что было передано. Один из способов сделать это - зашифровать передаваемые данные. В данной статье я предлагаю посмотреть как можно передать строку, зашифрованную одним из самых распространённых алгоритмом шифрования AES, из приложения ASP.NET в код JavaScript.
Для примера сделаем онлайн-просмотрщик PDF-файлов и зашифруем название отображаемого файла с целью предотвращения его загрузки клиентом.
Что нам понадобится:
- ASP.NET WebForms в качестве базы (для простоты примера)
- PDF.js для рендеринга файлов PDF
- CryptoJS для дешифровки пути к файлу
Приложение будет состоять из двух страничек: списка файлов и страницы просмотра выбранного документа.
Из ASP.NET в JavaScript нам понадобится передавать 3 параметра: зашифрованный путь к файлу будем передавать в querystring, данные для расшифровки будем передавать в параметрах SPECIAL и SALT, пароль будет статическим "PDFJSCS".
Итак, начнём
Основную работу по шифрованию будет делать класс Crypto
public class Crypto
{
private RijndaelManaged myRijndael = new RijndaelManaged();
private int iterations;
private byte[] salt;
public Crypto(string strPassword, string specString, string saltString)
{
myRijndael.BlockSize = 128;
myRijndael.KeySize = 128;
myRijndael.IV = HexStringToByteArray(specString);
myRijndael.Padding = PaddingMode.PKCS7;
myRijndael.Mode = CipherMode.CBC;
iterations = 1000;
salt = System.Text.Encoding.UTF8.GetBytes(saltString);
myRijndael.Key = GenerateKey(strPassword);
}
public string Encrypt(string strPlainText)
{
byte[] strText = new System.Text.UTF8Encoding().GetBytes(strPlainText);
ICryptoTransform transform = myRijndael.CreateEncryptor();
byte[] cipherText = transform.TransformFinalBlock(strText, 0, strText.Length);
return Convert.ToBase64String(cipherText);
}
public string Decrypt(string encryptedText)
{
dynamic encryptedBytes = Convert.FromBase64String(encryptedText);
ICryptoTransform transform = myRijndael.CreateDecryptor();
byte[] cipherText = transform.TransformFinalBlock(encryptedBytes, 0, encryptedBytes.Length);
return System.Text.Encoding.UTF8.GetString(cipherText);
}
public static byte[] HexStringToByteArray(string strHex)
{
byte[] r = new byte[strHex.Length / 2];
for (int i = 0; i <= strHex.Length - 1; i += 2)
r[i / 2] = Convert.ToByte(Convert.ToInt32(strHex.Substring(i, 2), 16));
return r;
}
private byte[] GenerateKey(string strPassword)
{
Rfc2898DeriveBytes rfc2898 = new Rfc2898DeriveBytes(System.Text.Encoding.UTF8.GetBytes(strPassword), salt, iterations);
return rfc2898.GetBytes(128 / 8);
}
private static string RandomString(int length)
{
const string chars = "abcdef0123456789";
Random random = new Random();
string result = string.Empty;
while (result.Length < length)
result += chars[random.Next(chars.Length)];
return result;
}
}
Стартовая страница будет содержать лишь один div, в который мы будем выводить список файлов
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Изучение PDF.JS</title>
</head>
<body>
<form id="formMain" runat="server">
<div style="margin: 25502550; font-size: 14pt;">
<h1>Содержимое папки /PDF</h1>
<hr />
<div id="divFiles" runat="server"></div>
<hr />
</div>
</form>
</body>
</html>
Back-end код страницы будет содержать два метода генерации случайных значений хэша и вектора
private static string RandomSpecString(int length)
{
const string chars = "abcdef0123456789";
Random random = new Random();
string result = string.Empty;
while (result.Length < length)
result += chars[random.Next(chars.Length)];
return result;
}
private static string RandomSaltString(int length)
{
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
Random random = new Random();
string result = string.Empty;
while (result.Length < length)
result += chars[random.Next(chars.Length)];
return result;
}
При загрузке страницы генерируется список файлов и зашифрованные ссылки на их просмотр, строки SPECIAL и SALT нам понадобятся для расшифровки, сохраним их
protected void Page_Load(object sender, EventArgs e)
{
string path = Path.Combine(HttpContext.Current.Server.MapPath("~"), "PDF");
string password = "PDFJSCS";
if (Session["SPECIAL"] == null)
Session["SPECIAL"] = RandomSpecString(32);
if (Session["SALT"] == null)
Session["SALT"] = RandomSaltString(16);
Crypto crypto = new Crypto(password, Session["SPECIAL"].ToString(), Session["SALT"].ToString());
// файлы в папке PDF
List<string> files = new List<string>(Directory.EnumerateFiles(path));
for (int i = 0; i < files.Count(); i++)
{
string fileName = files[i].Split('\\').Last().ToLower(); // имя файла
fileName = fileName.Substring(0, fileName.IndexOf(".pdf"));
FileInfo fInfo = new FileInfo(files[i]); // данные о файле
string filePath = Path.Combine("PDF", fInfo.Name); // путь к файлу
// номер строки
Label count = new Label();
count.Text = string.Format("{0:D3}. ", i + 1);
count.Style.Add("margin", "5 5 15 5");
// ссылка
HyperLink link = new HyperLink();
link.Target = "_blank";
link.NavigateUrl = string.Format("Viewer.aspx?path={0}", crypto.Encrypt(filePath));
link.Text = fileName;
// размер файла в Кб
Label size = new Label();
size.Text = string.Format(" ({0} Кб)", fInfo.Length / 1024);
size.Style.Add("margin", "5 5 15 5");
this.divFiles.Controls.Add(count);
this.divFiles.Controls.Add(link);
this.divFiles.Controls.Add(size);
this.divFiles.Controls.Add(new LiteralControl("<br />")); // перенос строки
}
}
Код страницы просмотрщика практически полностью берётся из pdfjs/web/viewer.html , добавим только в разделе head страницы объявления двух переменных необходимых для расшифровки строки и ссылки на CriptoJS и нашу pathtool.js, которая будет заниматься расшифровкой
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Просмотр PDF-документов</title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
<meta name="google" content="notranslate" />
<link rel="stylesheet" href="pdfjs/web/viewer.css" />
<link rel="stylesheet" href="css/print.css" />
<!-- расшифровка -->
<script type="text/javascript" src="js/pathtool.js"></script>
<!-- crypto-js -->
<script type="text/javascript" src="crypto-js/core.js"></script>
<script type="text/javascript" src="crypto-js/aes.js"></script>
<script type="text/javascript" src="crypto-js/pbkdf2.js"></script>
<!-- pdf.js -->
<script type="text/javascript" src="pdfjs/web/compatibility.js"></script>
<link rel="resource" type="application/l10n" href="pdfjs/web/locale/locale.properties" />
<script type="text/javascript" src="pdfjs/web/l10n.js"></script>
<script type="text/javascript" src="pdfjs/build/pdf.js"></script>
<!-- объявление и присвеоние значений переменным для расшифровки -->
<script type="text/javascript">
var SPECIAL = '<%= SPECIAL %>';
var SALT = '<%= SALT %>';
</script>
<script type="text/javascript" src="pdfjs/web/debugger.js"></script>
<script type="text/javascript" src="pdfjs/web/viewer.js"></script>
</head>
<body tabindex="1" class="loadingInProgress">
<div id="outerContainer">
<div id="sidebarContainer">
<div id="toolbarSidebar">
<div class="splitToolbarButton toggled">
<button id="viewThumbnail" class="toolbarButton group toggled" title="Show Thumbnails" tabindex="2" data-l10n-id="thumbs">
<span data-l10n-id="thumbs_label">Thumbnails</span>
</button>
<button id="viewOutline" class="toolbarButton group" title="Show Document Outline" tabindex="3" data-l10n-id="outline">
<span data-l10n-id="outline_label">Document Outline</span>
</button>
<button id="viewAttachments" class="toolbarButton group" title="Show Attachments" tabindex="4" data-l10n-id="attachments">
<span data-l10n-id="attachments_label">Attachments</span>
</button>
</div>
</div>
<div id="sidebarContent">
<div id="thumbnailView">
</div>
<div id="outlineView" class="hidden">
</div>
<div id="attachmentsView" class="hidden">
</div>
</div>
</div> <!-- sidebarContainer -->
<div id="mainContainer">
<div class="findbar hidden doorHanger hiddenSmallView" id="findbar">
<label for="findInput" class="toolbarLabel" data-l10n-id="find_label">Find:</label>
<input id="findInput" class="toolbarField" tabindex="91">
<div class="splitToolbarButton">
<button class="toolbarButton findPrevious" title="" id="findPrevious" tabindex="92" data-l10n-id="find_previous">
<span data-l10n-id="find_previous_label">Previous</span>
</button>
<div class="splitToolbarButtonSeparator"></div>
<button class="toolbarButton findNext" title="" id="findNext" tabindex="93" data-l10n-id="find_next">
<span data-l10n-id="find_next_label">Next</span>
</button>
</div>
<input type="checkbox" id="findHighlightAll" class="toolbarField" tabindex="94">
<label for="findHighlightAll" class="toolbarLabel" data-l10n-id="find_highlight">Highlight all</label>
<input type="checkbox" id="findMatchCase" class="toolbarField" tabindex="95">
<label for="findMatchCase" class="toolbarLabel" data-l10n-id="find_match_case_label">Match case</label>
<span id="findMsg" class="toolbarLabel"></span>
</div> <!-- findbar -->
<div id="secondaryToolbar" class="secondaryToolbar hidden doorHangerRight">
<div id="secondaryToolbarButtonContainer">
<button id="secondaryPresentationMode" class="secondaryToolbarButton presentationMode visibleLargeView" title="Switch to Presentation Mode" tabindex="51" data-l10n-id="presentation_mode">
<span data-l10n-id="presentation_mode_label">Presentation Mode</span>
</button>
<button id="secondaryOpenFile" class="secondaryToolbarButton openFile visibleLargeView" title="Open File" tabindex="52" data-l10n-id="open_file">
<span data-l10n-id="open_file_label">Open</span>
</button>
<button id="secondaryPrint" class="secondaryToolbarButton print visibleMediumView" title="Print" tabindex="53" data-l10n-id="print">
<span data-l10n-id="print_label">Print</span>
</button>
<button id="secondaryDownload" class="secondaryToolbarButton download visibleMediumView" title="Download" tabindex="54" data-l10n-id="download">
<span data-l10n-id="download_label">Download</span>
</button>
<a href="#" id="secondaryViewBookmark" class="secondaryToolbarButton bookmark visibleSmallView" title="Current view (copy or open in new window)" tabindex="55" data-l10n-id="bookmark">
<span data-l10n-id="bookmark_label">Current View</span>
</a>
<div class="horizontalToolbarSeparator visibleLargeView"></div>
<button id="firstPage" class="secondaryToolbarButton firstPage" title="Go to First Page" tabindex="56" data-l10n-id="first_page">
<span data-l10n-id="first_page_label">Go to First Page</span>
</button>
<button id="lastPage" class="secondaryToolbarButton lastPage" title="Go to Last Page" tabindex="57" data-l10n-id="last_page">
<span data-l10n-id="last_page_label">Go to Last Page</span>
</button>
<div class="horizontalToolbarSeparator"></div>
<button id="pageRotateCw" class="secondaryToolbarButton rotateCw" title="Rotate Clockwise" tabindex="58" data-l10n-id="page_rotate_cw">
<span data-l10n-id="page_rotate_cw_label">Rotate Clockwise</span>
</button>
<button id="pageRotateCcw" class="secondaryToolbarButton rotateCcw" title="Rotate Counterclockwise" tabindex="59" data-l10n-id="page_rotate_ccw">
<span data-l10n-id="page_rotate_ccw_label">Rotate Counterclockwise</span>
</button>
<div class="horizontalToolbarSeparator"></div>
<button id="toggleHandTool" class="secondaryToolbarButton handTool" title="Enable hand tool" tabindex="60" data-l10n-id="hand_tool_enable">
<span data-l10n-id="hand_tool_enable_label">Enable hand tool</span>
</button>
<div class="horizontalToolbarSeparator"></div>
<button id="documentProperties" class="secondaryToolbarButton documentProperties" title="Document Properties…" tabindex="61" data-l10n-id="document_properties">
<span data-l10n-id="document_properties_label">Document Properties…</span>
</button>
</div>
</div> <!-- secondaryToolbar -->
<div class="toolbar">
<div id="toolbarContainer">
<div id="toolbarViewer">
<div id="toolbarViewerLeft">
<button id="sidebarToggle" class="toolbarButton" title="Toggle Sidebar" tabindex="11" data-l10n-id="toggle_sidebar">
<span data-l10n-id="toggle_sidebar_label">Toggle Sidebar</span>
</button>
<div class="toolbarButtonSpacer"></div>
<button id="viewFind" class="toolbarButton group hiddenSmallView" title="Find in Document" tabindex="12" data-l10n-id="findbar">
<span data-l10n-id="findbar_label">Find</span>
</button>
<div class="splitToolbarButton">
<button class="toolbarButton pageUp" title="Previous Page" id="previous" tabindex="13" data-l10n-id="previous">
<span data-l10n-id="previous_label">Previous</span>
</button>
<div class="splitToolbarButtonSeparator"></div>
<button class="toolbarButton pageDown" title="Next Page" id="next" tabindex="14" data-l10n-id="next">
<span data-l10n-id="next_label">Next</span>
</button>
</div>
<label id="pageNumberLabel" class="toolbarLabel" for="pageNumber" data-l10n-id="page_label">Page: </label>
<input type="number" id="pageNumber" class="toolbarField pageNumber" value="1" size="4" min="1" tabindex="15">
<span id="numPages" class="toolbarLabel"></span>
</div>
<div id="toolbarViewerRight">
<button id="presentationMode" class="toolbarButton presentationMode hiddenLargeView" title="Switch to Presentation Mode" tabindex="31" data-l10n-id="presentation_mode">
<span data-l10n-id="presentation_mode_label">Presentation Mode</span>
</button>
<button id="openFile" class="toolbarButton openFile hiddenLargeView" title="Open File" tabindex="32" data-l10n-id="open_file">
<span data-l10n-id="open_file_label">Open</span>
</button>
<button id="print" class="toolbarButton print hiddenMediumView" title="Print" tabindex="33" data-l10n-id="print" hidden>
<span data-l10n-id="print_label">Print</span>
</button>
<button id="download" class="toolbarButton download hiddenMediumView" title="Download" tabindex="34" data-l10n-id="download" hidden>
<span data-l10n-id="download_label">Download</span>
</button>
<a href="#" id="viewBookmark" class="toolbarButton bookmark hiddenSmallView" title="Current view (copy or open in new window)" tabindex="35" data-l10n-id="bookmark">
<span data-l10n-id="bookmark_label">Current View</span>
</a>
<div class="verticalToolbarSeparator hiddenSmallView"></div>
<button id="secondaryToolbarToggle" class="toolbarButton" title="Tools" tabindex="36" data-l10n-id="tools">
<span data-l10n-id="tools_label">Tools</span>
</button>
</div>
<div class="outerCenter">
<div class="innerCenter" id="toolbarViewerMiddle">
<div class="splitToolbarButton">
<button id="zoomOut" class="toolbarButton zoomOut" title="Zoom Out" tabindex="21" data-l10n-id="zoom_out">
<span data-l10n-id="zoom_out_label">Zoom Out</span>
</button>
<div class="splitToolbarButtonSeparator"></div>
<button id="zoomIn" class="toolbarButton zoomIn" title="Zoom In" tabindex="22" data-l10n-id="zoom_in">
<span data-l10n-id="zoom_in_label">Zoom In</span>
</button>
</div>
<span id="scaleSelectContainer" class="dropdownToolbarButton">
<select id="scaleSelect" title="Zoom" tabindex="23" data-l10n-id="zoom">
<option id="pageAutoOption" title="" value="auto" selected="selected" data-l10n-id="page_scale_auto">Automatic Zoom</option>
<option id="pageActualOption" title="" value="page-actual" data-l10n-id="page_scale_actual">Actual Size</option>
<option id="pageFitOption" title="" value="page-fit" data-l10n-id="page_scale_fit">Fit Page</option>
<option id="pageWidthOption" title="" value="page-width" data-l10n-id="page_scale_width">Full Width</option>
<option id="customScaleOption" title="" value="custom"></option>
<option title="" value="0.5" data-l10n-id="page_scale_percent" data-l10n-args='{ "scale": 50 }'>50%</option>
<option title="" value="0.75" data-l10n-id="page_scale_percent" data-l10n-args='{ "scale": 75 }'>75%</option>
<option title="" value="1" data-l10n-id="page_scale_percent" data-l10n-args='{ "scale": 100 }'>100%</option>
<option title="" value="1.25" data-l10n-id="page_scale_percent" data-l10n-args='{ "scale": 125 }'>125%</option>
<option title="" value="1.5" data-l10n-id="page_scale_percent" data-l10n-args='{ "scale": 150 }'>150%</option>
<option title="" value="2" data-l10n-id="page_scale_percent" data-l10n-args='{ "scale": 200 }'>200%</option>
<option title="" value="3" data-l10n-id="page_scale_percent" data-l10n-args='{ "scale": 300 }'>300%</option>
<option title="" value="4" data-l10n-id="page_scale_percent" data-l10n-args='{ "scale": 400 }'>400%</option>
</select>
</span>
</div>
</div>
</div>
<div id="loadingBar">
<div class="progress">
<div class="glimmer">
</div>
</div>
</div>
</div>
</div>
<menu type="context" id="viewerContextMenu">
<menuitem id="contextFirstPage" label="First Page"
data-l10n-id="first_page"></menuitem>
<menuitem id="contextLastPage" label="Last Page"
data-l10n-id="last_page"></menuitem>
<menuitem id="contextPageRotateCw" label="Rotate Clockwise"
data-l10n-id="page_rotate_cw"></menuitem>
<menuitem id="contextPageRotateCcw" label="Rotate Counter-Clockwise"
data-l10n-id="page_rotate_ccw"></menuitem>
</menu>
<div id="viewerContainer" tabindex="0">
<div id="viewer" class="pdfViewer"></div>
</div>
<div id="errorWrapper" hidden='true'>
<div id="errorMessageLeft">
<span id="errorMessage"></span>
<button id="errorShowMore" data-l10n-id="error_more_info">
More Information
</button>
<button id="errorShowLess" data-l10n-id="error_less_info" hidden='true'>
Less Information
</button>
</div>
<div id="errorMessageRight">
<button id="errorClose" data-l10n-id="error_close">
Close
</button>
</div>
<div class="clearBoth"></div>
<textarea id="errorMoreInfo" hidden='true' readonly="readonly"></textarea>
</div>
</div> <!-- mainContainer -->
<div id="overlayContainer" class="hidden">
<div id="passwordOverlay" class="container hidden">
<div class="dialog">
<div class="row">
<p id="passwordText" data-l10n-id="password_label">Enter the password to open this PDF file:</p>
</div>
<div class="row">
<input type="password" id="password" class="toolbarField" />
</div>
<div class="buttonRow">
<button id="passwordCancel" class="overlayButton"><span data-l10n-id="password_cancel">Cancel</span></button>
<button id="passwordSubmit" class="overlayButton"><span data-l10n-id="password_ok">OK</span></button>
</div>
</div>
</div>
<div id="documentPropertiesOverlay" class="container hidden">
<div class="dialog">
<div class="row">
<span data-l10n-id="document_properties_file_name">File name:</span> <p id="fileNameField">-</p>
</div>
<div class="row">
<span data-l10n-id="document_properties_file_size">File size:</span> <p id="fileSizeField">-</p>
</div>
<div class="separator"></div>
<div class="row">
<span data-l10n-id="document_properties_title">Title:</span> <p id="titleField">-</p>
</div>
<div class="row">
<span data-l10n-id="document_properties_author">Author:</span> <p id="authorField">-</p>
</div>
<div class="row">
<span data-l10n-id="document_properties_subject">Subject:</span> <p id="subjectField">-</p>
</div>
<div class="row">
<span data-l10n-id="document_properties_keywords">Keywords:</span> <p id="keywordsField">-</p>
</div>
<div class="row">
<span data-l10n-id="document_properties_creation_date">Creation Date:</span> <p id="creationDateField">-</p>
</div>
<div class="row">
<span data-l10n-id="document_properties_modification_date">Modification Date:</span> <p id="modificationDateField">-</p>
</div>
<div class="row">
<span data-l10n-id="document_properties_creator">Creator:</span> <p id="creatorField">-</p>
</div>
<div class="separator"></div>
<div class="row">
<span data-l10n-id="document_properties_producer">PDF Producer:</span> <p id="producerField">-</p>
</div>
<div class="row">
<span data-l10n-id="document_properties_version">PDF Version:</span> <p id="versionField">-</p>
</div>
<div class="row">
<span data-l10n-id="document_properties_page_count">Page Count:</span> <p id="pageCountField">-</p>
</div>
<div class="buttonRow">
<button id="documentPropertiesClose" class="overlayButton"><span data-l10n-id="document_properties_close">Close</span></button>
</div>
</div>
</div>
</div> <!-- overlayContainer -->
</div> <!-- outerContainer -->
</body>
</html>
Back-end код страницы просмотрщика содержит лишь два свойства
public string SPECIAL { get { return Session["SPECIAL"].ToString(); } }
public string SALT { get { return Session["SALT"].ToString(); } }
Объект PATHTOOL.js, дешифрующий строку
function PATHTOOL()
{
this.PASSWORD = "PDFJSCS";
this.SPECIAL = "";
this.SALT = "";
this.decrypt = function (ENCRYPTED) {
let decryptedText = null;
try {
//Creating the Vector Key
let iv = CryptoJS.enc.Hex.parse(this.SPECIAL);
//Encoding the Password in from UTF8 to byte array
let Pass = CryptoJS.enc.Utf8.parse(this.PASSWORD);
//Encoding the Salt in from UTF8 to byte array
let Salt = CryptoJS.enc.Utf8.parse(this.SALT);
//Creating the key in PBKDF2 format to be used during the decryption
let key128Bits1000Iterations = CryptoJS.PBKDF2(Pass.toString(CryptoJS.enc.Utf8), Salt, { keySize: 128 / 32, iterations: 1000 });
//Enclosing the test to be decrypted in a CipherParams object as supported by the CryptoJS libarary
let cipherParams = CryptoJS.lib.CipherParams.create({
ciphertext: CryptoJS.enc.Base64.parse(ENCRYPTED)
});
//Decrypting the string contained in cipherParams using the PBKDF2 key
let decrypted = CryptoJS.AES.decrypt(cipherParams, key128Bits1000Iterations, { mode: CryptoJS.mode.CBC, iv: iv, padding: CryptoJS.pad.Pkcs7 });
decryptedText = decrypted.toString(CryptoJS.enc.Utf8);
return decryptedText;
}
catch (err) { //Malformed UTF Data due to incorrect password
return "Ошибка дешифровки строки! ;(";
}
};
}
В pdfjs/web/viewer.js добавим две функции: обработка querystring и дешифровка пути к файлу
// Расшифровка пути и имени файла
function getFilePath()
{
let path = new PATHTOOL();
path.SPECIAL = SPECIAL;
path.SALT = SALT;
let encrypted = getParameterByName('path');
return path.decrypt(encrypted);
}
// querystring
// http://stackoverflow.com/a/901144
function getParameterByName(name, url) {
if (!url) url = window.location.href;
name = name.replace(/[\[\]]/g, "\\$&");
var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
results = regex.exec(url);
if (!results) return null;
if (!results[2]) return '';
return decodeURIComponent(results[2]);
}
, строку
var DEFAULT_URL = 'compressed.tracemonkey-pldi-09.pdf';
поменяем на
var DEFAULT_URL = getFilePath();
Готово! Запустим приложение
Исходники находятся во вложении.