分类 折腾 下的文章

分析 TW DNS RPZ 网络封锁与解锁策略,域名遭劫持时常见“此网域已经遭到封锁”提示,厘清政府管控机制并使用 DoH、VPN 等方式绕过限制,并评估政策成效与隐私风险,归纳成人内容与盗版网站如何受此影响,透过实例与图解说明 DNS 拦截运作机制与自订浏览器、手机 DoH 配置,说明自由与审查交错下的种种冲击。

前言

艺人黄子佼近期卷入桃色风波,该事件引发广泛关注并促使媒体报导“创意私房已关闭”的消息。许多出于好奇心尝试访问该网站的使用者,却发现其网址遭到劫持,最终被导向台湾网络资讯中心(TWNIC)设置的封锁页面。

台湾网络资讯中心(Taiwan network information center,TWNIC)是管理台湾网域(.tw、.台湾)与 IP 位址的机构,负责网络资源分配、技术教育推广、国际合作及网络安全发展。

网络自由

网络本质上是自由的,这里所谓的自由指的是使用者能够连线到任意网站。然而,受到法规限制的影响,政府可能会控制或禁止部分特定网页的存取。例如,在中国大陆,常见的被封锁对象包括 GoogleYouTubeFacebook维基百科

网络自由并不意味着对色情、赌博、盗版等行为的容忍或鼓励,尤其当这些活动建立在剥削他人痛苦或侵犯他人权益的基础上时。这些行为本质上就不应受到支持或许可,且若有人因接触此类内容而进一步涉入犯罪或非法活动,将可能引发更严重的社会问题。

而台湾早在 2013 年,因应国内外部分网站提供侵权内容的情况,智慧财产局曾建议国内 ISP 采用 DNS 或封锁 IP 的方式进行阻挡,但最终未能落实。

在近期新闻媒体的渲染下,当使用者点选创意私房时,便会发现其网址被转导至封锁页面。事实上,自 2020-03-30 日起,TWNIC(财团法人台湾网络资讯中心)召开了第一次 DNS RPZ 会议,并透过 DNS 等方式进行网络封锁持续至今。

回应政策区域(DNS RPZ)

什么是 DNS RPZ

域名系统回应政策区域(domain name system response policy zone,DNS RPZ)是一项由 互联网系统联盟 开发的技术,旨在于 DNS 服务器层级实现网址过滤机制。

RPZ 允许网络管理员在 DNS 服务器中配置特殊的回应政策区域,当查询涉及受限制的域名时,该 DNS 服务器将依据预设策略,回传指定的 IP 位址或错误讯息,借此将流量重定向或阻断,从而有效防范访问恶意网站、钓鱼网站或政治不正确的内容。

举例而言,若使用者欲浏览 google.com,其正确 IP 位址为 1.2.3.4,但若该网域已被列入中华电信(DNS 服务器 IP:168.95.1.1)的 RPZ 过滤名单中,DNS 解析后将回传 NXDOMAIN 或被重定向至特定 IP 位址,导致无法正常取得该网站内容。

相对地,若将 DNS 服务器改为使用未参与 RPZ 过滤的公共 DNS 服务(如 CloudFlare 的 1.1.1.1),则在解析 google.com 时不受过滤名单影响,能够正常解析并取得该网站的原始内容。

谁使用了 TWNIC 的 RPZ?

根据 2024/04 撷取自TWNIC的资料,目前已参与该机制的成员包括:

  1. 教育部(Ministry of Education Republic of China)

  2. 中华电信(Chunghwa Telecom)

  3. 台湾硕网(So-net Taiwan)

  4. 宏远电讯(SaveCom International)

  5. 中嘉和网(KBT)

  6. 大台中数位有线电视(VeeTIME Corp.)

  7. 天外天数位有线电视(TWT Digital Communication Corporation)

  8. 正源科技(Yulon IT Solutions)

  9. 台湾固网(台湾大哥大)(Taiwan Fixed Network)

  10. 新世纪资通(远传)(New Century InfoComm Tech,NCIC)

  11. 亚太电信(Asia Pacific Telecom,APT)

  12. 台湾之星(Taiwan Star Telecom,T Star)

  13. 三大有线电视(SAN DA CATV)

  14. 公共电视(Public Television Service,PTS)

  15. 统一资讯(President Information)

换言之,如果使用的是中华电信、台湾固网、远传等网络服务,其预设的 DNS 服务器皆会透过 RPZ 进行过滤。

RPZ 安全吗?

域名解析的核心功能在于将网址转换成相应的 IP 位址。当我们将网址提交给 DNS 服务器时,该服务器仅会记录使用者请求的域名,而不会接触到网页内容或传输的敏感资讯(例如密码、个人资料)。DNS 服务器会回传经过过滤或确认安全的 IP 位址,确保使用者能够安全地连线。

例如,当使用者尝试浏览 NSFW.com,若其域名遭到劫持而回传 150.242.101.120,浏览器将比对该连线的凭证。若凭证与预期域名不一致或连线使用 HTTP 协定,则会显示不安全提示。使用者若选择忽略警告,则最终会被重定向到 TWNIC 设置的封锁页面。

此外,若电脑的“受信任根凭证”遭窜改,即使域名被劫持且信任了可疑凭证,浏览器也可能无法发出有效警告,从而使攻击者得以执行中间人攻击(MITM),进一步拦截和解析所有网页传输内容,实施更精确的封锁。因此,在使用公共电脑时,建议避免进行登入等敏感操作,以降低安全风险。

相关应用

对于相关应用,使用者可以自行建立 DNS 服务器来过滤广告、病毒、色情及钓鱼网站等不良内容,自建的 DNS 服务器在功能上与 RPZ 相似。目前市场上有现成方案,例如免费的 NextDNS 或可自行架设的开源工具 AdGuardHome

如何避免网络封锁

上述内容说明,实际上只是 DNS 解析中被套用一层 RPZ 而已。若要正常浏览网站,建议不要使用这些机构预设所提供的 DNS 服务器。较新一代的系统可以透过设定 DNS over HTTPS(DoH)来防范攻击者伪造 DNS 讯息。从网络管理员的角度来看,DoH 流量会与其他 HTTPS 流量相同,进而使网管更难追踪使用者浏览的网页。

浏览器端

步骤 1:开启浏览器的设定

步骤 2:搜寻并输入 DNS,填入 DoH 网址后,即可正常解析网页。

手机端

在手机装置上,大部分新系统均支援 DNS over HTTPS(DoH)。以 iPhone 为例,可透过安装 mobileconfig 设定档 来启用此功能。安装完成后,手机所有流量将透过指定的 DNS 服务器进行解析,您可以选择 Google、Cloudflare 或 Quad9 等公认的 DNS 服务器。

  1. 点击上述连结后,选择允许安装描述档。

  2. 前往系统中的“设定”→“一般”→“VPN 与装置管理”。

  3. 在“已下载的描述档”中,找到 Cloudflare DNS over HTTPS 描述档,然后点选右上角的“安装”。

  4. 进入“DNS”设定,将“自动”切换为“Cloudflare DNS over HTTPS”。

完成以上步骤后,您的手机将以 DoH 方式查询域名,有效避免 DNS 劫持并提升使用者隐私保护。

备注: 请注意,DNS over HTTPS(DoH)仅为加密 DNS 查询的技术,并不等同于虚拟私人网络(VPN)。

结论

网络审查是一项极为复杂的议题,涉及政府、企业与使用者之间的持续对抗。以大陆为例,使用者冒着违法风险利用 VPN,并结合各种加密协定(如 Shadowsocks、V2ray、Trojan、Hysteria、Juicity、WireGuard、Snell)进行翻墙;而在台湾,市面上充斥着各式 VPN 服务,付费订阅后即可绕过相关限制,最终仅使 VPN 供应商、中间商与 VPS 供应商获利。

此外,若网站遭封锁,网站主只需更换域名,即可让政府难以及时封锁。甚至透过 TG、Line、FB 与各类私密群组分享、贩售该类内容,使得封锁措施形同虚设。正如新闻媒体渲染的“创意私房已关闭”讯息,反而令更多人认识该网站。

成人内容产业涉及广泛的伦理与法律问题,但当市场需求存在时,即使花费巨额资金取得内容,产业仍难以彻底瓦解。真正的解决之道在于释出高额奖金以捉拿幕后操控者(例如拍摄者或诈骗广告投递者),并实施严格法律制裁;对于屡次违规者,则采取全面封锁措施。

然而,色情产业是否能够真正根除仍存疑问:想观看的人总能找到方法,甚至不惜支付更高金额获得内容。值得一提的是,Apple 于 2021 年曾提出儿童性虐待素材(child sexual abuse material,CSAM)检测系统,计划透过比对使用者 iCloud 照片与儿童保护组织提供的已知 CSAM 图片杂凑值来侦测相关素材,但最终因争议与准确性问题而未获推行。

期盼未来能有更完善的措施,减少因胁迫而拍摄的不良影片与缺乏内容、没营养价值的诈骗垃圾广告,让这个世界变得更加美好。

参考文献

  1. 全球最大最知名 .tw 网站一指被封 – T.H. Schee

  2. 公共域名解析服务 - 维基百科

  3. 什么是 DNS – DNS 简介

  4. DNS Firewall : Response Policy Zone – SecurityZones

  5. 什么是 DNS 快取内存中毒? | DNS 诈骗

  6. 经济部智慧财产局研拟封锁境外侵权网站事件 - 维基百科

  7. CSAM Detection Technical Summary

  8. 创意私房 - 维基百科

在AI兴起之前,一直在用的代码,分享给盆友们~

Mozilla Readability.js 的 PHP 端口。解析 html(通常是新闻和文章)返回标题、作者、主图像和文本内容(最新版可用属性)。分析每个节点,给它们打分,并确定哪些是相关的,哪些可以丢弃。

算法原作者:https://github.com/mingcheng/php-readability/

算法新版:https://github.com/andreskrey/readability.php(该项目已经废弃)

算法最新版:https://github.com/fivefilters/readability.php

require 'Readability.php';
$html = "整个html页面";
$Readability     = new Readability($html);
$ReadabilityData = $Readability->getContent();

//var_dump($ReadabilityData);
echo "<h1>".$ReadabilityData['title']."</h1>";
echo $ReadabilityData['content'];

Readability.php

class Readability {
	// 保存判定结果的标记位名称
	const ATTR_CONTENT_SCORE = "contentScore";

	// DOM 解析类目前只支持 UTF-8 编码
	const DOM_DEFAULT_CHARSET = "utf-8";

	// 当判定失败时显示的内容
	const MESSAGE_CAN_NOT_GET = "Readability was unable to parse this page for content.";

	// DOM 解析类(PHP5 已内置)
	protected $DOM = null;

	// 需要解析的源代码
	protected $source = "";

	// 章节的父元素列表
	private $parentNodes = array();

	// 需要删除的标签
	// Note: added extra tags from https://github.com/ridcully
	private $junkTags = Array("style", "form", "iframe", "script", "button", "input", "textarea", 
								"noscript", "select", "option", "object", "applet", "basefont",
								"bgsound", "blink", "canvas", "command", "menu", "nav", "datalist",
								"embed", "frame", "frameset", "keygen", "label", "marquee", "link");

	// 需要删除的属性
	private $junkAttrs = Array("style", "class", "onclick", "onmouseover", "align", "border", "margin", "id", "contentScore");


	/**
	 * 构造函数
	 *	  @param $input_char 字符串的编码。默认 utf-8,可以省略
	 */
	function __construct($source, $input_char = "utf-8") {
		$this->source = $source;

		// DOM 解析类只能处理 UTF-8 格式的字符
		$source = mb_convert_encoding($source, 'HTML-ENTITIES', $input_char);

		// 预处理 HTML 标签,剔除冗余的标签等
		$source = $this->preparSource($source);

		// 生成 DOM 解析类
		$this->DOM = new DOMDocument('1.0', $input_char);
		try {
			//libxml_use_internal_errors(true);
			// 会有些错误信息,不过不要紧 :^)
			if (!@$this->DOM->loadHTML('<?xml encoding="'.Readability::DOM_DEFAULT_CHARSET.'">'.$source)) {
				throw new Exception("Parse HTML Error!");
			}

			foreach ($this->DOM->childNodes as $item) {
				if ($item->nodeType == XML_PI_NODE) {
					$this->DOM->removeChild($item); // remove hack
				}
			}

			// insert proper
			$this->DOM->encoding = Readability::DOM_DEFAULT_CHARSET;
		} catch (Exception $e) {
			// ...
		}
	}


	/**
	 * 预处理 HTML 标签,使其能够准确被 DOM 解析类处理
	 *
	 * @return String
	 */
	private function preparSource($string) {
		// 剔除多余的 HTML 编码标记,避免解析出错
		preg_match("/charset=([\w|\-]+);?/", $string, $match);
		if (isset($match[1])) {
			$string = preg_replace("/charset=([\w|\-]+);?/", "", $string, 1);
		}

		// Replace all doubled-up <BR> tags with <P> tags, and remove fonts.
		$string = preg_replace("/<br\/?>[ \r\n\s]*<br\/?>/i", "</p><p>", $string);
		$string = preg_replace("/<\/?font[^>]*>/i", "", $string);

		// @see https://github.com/feelinglucky/php-readability/issues/7
		//   - from http://stackoverflow.com/questions/7130867/remove-script-tag-from-html-content
		$string = preg_replace("#<script(.*?)>(.*?)</script>#is", "", $string);

		return trim($string);
	}


	/**
	 * 删除 DOM 元素中所有的 $TagName 标签
	 *
	 * @return DOMDocument
	 */
	private function removeJunkTag($RootNode, $TagName) {
		
		$Tags = $RootNode->getElementsByTagName($TagName);
		
		//Note: always index 0, because removing a tag removes it from the results as well.
		while($Tag = $Tags->item(0)){
			$parentNode = $Tag->parentNode;
			$parentNode->removeChild($Tag);
		}
		
		return $RootNode;
		
	}

	/**
	 * 删除元素中所有不需要的属性
	 */
	private function removeJunkAttr($RootNode, $Attr) {
		$Tags = $RootNode->getElementsByTagName("*");

		$i = 0;
		while($Tag = $Tags->item($i++)) {
			$Tag->removeAttribute($Attr);
		}

		return $RootNode;
	}

	/**
	 * 根据评分获取页面主要内容的盒模型
	 *	  判定算法来自:http://code.google.com/p/arc90labs-readability/   
	 *	  这里由郑晓博客转发
	 * @return DOMNode
	 */
	private function getTopBox() {
		// 获得页面所有的章节
		$allParagraphs = $this->DOM->getElementsByTagName("p");

		// Study all the paragraphs and find the chunk that has the best score.
		// A score is determined by things like: Number of <p>'s, commas, special classes, etc.
		$i = 0;
		while($paragraph = $allParagraphs->item($i++)) {
			$parentNode   = $paragraph->parentNode;
			$contentScore = intval($parentNode->getAttribute(Readability::ATTR_CONTENT_SCORE));
			$className	= $parentNode->getAttribute("class");
			$id		   = $parentNode->getAttribute("id");

			// Look for a special classname
			if (preg_match("/(comment|meta|footer|footnote)/i", $className)) {
				$contentScore -= 50;
			} else if(preg_match(
				"/((^|\\s)(post|hentry|entry[-]?(content|text|body)?|article[-]?(content|text|body)?)(\\s|$))/i",
				$className)) {
				$contentScore += 25;
			}

			// Look for a special ID
			if (preg_match("/(comment|meta|footer|footnote)/i", $id)) {
				$contentScore -= 50;
			} else if (preg_match(
				"/^(post|hentry|entry[-]?(content|text|body)?|article[-]?(content|text|body)?)$/i",
				$id)) {
				$contentScore += 25;
			}

			// Add a point for the paragraph found
			// Add points for any commas within this paragraph
			if (strlen($paragraph->nodeValue) > 10) {
				$contentScore += strlen($paragraph->nodeValue);
			}

			// 保存父元素的判定得分
			$parentNode->setAttribute(Readability::ATTR_CONTENT_SCORE, $contentScore);

			// 保存章节的父元素,以便下次快速获取
			array_push($this->parentNodes, $parentNode);
		}

		$topBox = null;
		
		// Assignment from index for performance. 
		//	 See http://www.peachpit.com/articles/article.aspx?p=31567&seqNum=5 
		for ($i = 0, $len = sizeof($this->parentNodes); $i < $len; $i++) {
			$parentNode	  = $this->parentNodes[$i];
			$contentScore	= intval($parentNode->getAttribute(Readability::ATTR_CONTENT_SCORE));
			$orgContentScore = intval($topBox ? $topBox->getAttribute(Readability::ATTR_CONTENT_SCORE) : 0);

			if ($contentScore && $contentScore > $orgContentScore) {
				$topBox = $parentNode;
			}
		}
		
		// 此时,$topBox 应为已经判定后的页面内容主元素
		return $topBox;
	}


	/**
	 * 获取 HTML 页面标题
	 *
	 * @return String
	 */
	public function getTitle() {
		$split_point = ' - ';
		$titleNodes = $this->DOM->getElementsByTagName("title");

		if ($titleNodes->length 
			&& $titleNode = $titleNodes->item(0)) {
			// @see http://stackoverflow.com/questions/717328/how-to-explode-string-right-to-left
			$title  = trim($titleNode->nodeValue);
			$result = array_map('strrev', explode($split_point, strrev($title)));
			return sizeof($result) > 1 ? array_pop($result) : $title;
		}

		return null;
	}


	/**
	 * Get Leading Image Url
	 *
	 * @return String
	 */
	public function getLeadImageUrl($node) {
		$images = $node->getElementsByTagName("img");

		if ($images->length && $leadImage = $images->item(0)) {
			return $leadImage->getAttribute("src");
		}

		return null;
	}


	/**
	 * 获取页面的主要内容(Readability 以后的内容)
	 *
	 * @return Array
	 */
	public function getContent() {
		if (!$this->DOM) return false;

		// 获取页面标题
		$ContentTitle = $this->getTitle();

		// 获取页面主内容
		$ContentBox = $this->getTopBox();
		
		//Check if we found a suitable top-box.
		if($ContentBox === null)
			throw new RuntimeException(Readability::MESSAGE_CAN_NOT_GET);
		
		// 复制内容到新的 DOMDocument
		$Target = new DOMDocument;
		$Target->appendChild($Target->importNode($ContentBox, true));

		// 删除不需要的标签
		foreach ($this->junkTags as $tag) {
			$Target = $this->removeJunkTag($Target, $tag);
		}

		// 删除不需要的属性
		foreach ($this->junkAttrs as $attr) {
			$Target = $this->removeJunkAttr($Target, $attr);
		}

		$content = mb_convert_encoding($Target->saveHTML(), Readability::DOM_DEFAULT_CHARSET, "HTML-ENTITIES");

		// 多个数据,以数组的形式返回
		return Array(
			'lead_image_url' => $this->getLeadImageUrl($Target),
			'word_count' => mb_strlen(strip_tags($content), Readability::DOM_DEFAULT_CHARSET),
			'title' => $ContentTitle ? $ContentTitle : null,
			'content' => $content
		);
	}

	function __destruct() { }
}


在使用Typecho时,文章浏览次数统计算是最常用到的功能,把下面这段代码加到主题 functions.php 中,直接调用即可。代码加入了Cookie防刷新,这样文章浏览次数更真实。

function get_views($archive) {
    $db = Typecho_Db::get();
    $cid = $archive->cid;
    //添加 table.contents.views 字段
    if (!array_key_exists('views', $db->fetchRow($db->select()->from('table.contents')))) {
        $db->query('ALTER TABLE `'.$db->getPrefix().'contents` ADD `views` INT(10) DEFAULT 0;');
    }
    $row = $db->fetchRow($db->select('views')->from('table.contents')->where('cid = ?', $cid));
    $views = (int)$row['views'];
    if ($archive->is('single')) {
        $cookie = Typecho_Cookie::get('extend_views');
        $cookie = $cookie ? explode(',', $cookie) : array();
        if (!in_array($cid, $cookie)) {
            $db->query($db->update('table.contents')->rows(array('views' => $views+1))->where('cid = ?', $cid));
            $views = $views+1;
            array_push($cookie, $cid);
            $cookie = implode(',', $cookie);
            Typecho_Cookie::set('extend_views', $cookie);
        }
    }
    echo $views == 0 ? '暂无阅读' :'阅读量:' . $views;
}

调用代码:

<?php get_views($this) ?>

想要实现左右div等高,可以使用CSS的Flexbox布局或者Grid布局来实现。以下是2种方法的示例:

使用Flexbox布局:

<div class="container">
	<div class="left">左边内容</div>
	<div class="right">右边内容<br>右边内容</div>
</div>
<style>
.container {
	display: flex;
}
.left {
	background-color: red;
}
.right {
	background-color: Blue;
}
</style>

在上述示例中,通过将容器设置为Flexbox布局,并将左右两个子元素的flex属性设置为1,可以使它们平均分配容器的宽度,从而实现高度相等。

使用Grid布局:

<div class="container">
	<div class="left">左边内容</div>
	<div class="right">右边内容<br>右边内容</div>
</div>
<style>
.container {
	display: grid;
	grid-template-columns: 1fr 1fr;
}
.left, .right {
	height: 100%;
}
.left {
	background-color: red;
}
.right {
	background-color: Blue;
}
</style>

以上两种方法都可以实现左右两边的div高度相等,具体选择哪种方法取决于项目需求,本站主题使用的Flexbox布局!

id是网站中经常出现的,它一般是数字,但是我们发现现在的网站很多ID都是字母了,比如YouTube的视频播放页它的URL类似 /watch?v=bTT4lmzlFuE。下面是一个生成字母id,并可以逆转回数字id的方法,而且还可设置秘钥。示例:

alphaID(12354); //会将数字转换为字母ID
alphaID('PpQXn7COf', true); //会将字母ID转换为对应的数字ID
alphaID(12354, false, 6, "passKey"); //带秘钥指定生成字母ID的长度为6
alphaID('PpQXn7COf', true, 6, "passKey"); //带秘钥将字母ID转换为对应的数字ID
/**
 * Translates a number to a short alhanumeric version
 *
 * Translated any number up to 9007199254740992
 * to a shorter version in letters e.g.:
 * 9007199254740989 --> PpQXn7COf
 *
 * specifiying the second argument true, it will
 * translate back e.g.:
 * PpQXn7COf --> 9007199254740989
 *
 * this function is based on any2dec && dec2any by
 * fragmer[at]mail[dot]ru
 * see: http://nl3.php.net/manual/en/function.base-convert.php#52450
 *
 * If you want the alphaID to be at least 3 letter long, use the
 * $pad_up = 3 argument
 *
 * In most cases this is better than totally random ID generators
 * because this can easily avoid duplicate ID's.
 * For example if you correlate the alpha ID to an auto incrementing ID
 * in your database, you're done.
 *
 * The reverse is done because it makes it slightly more cryptic,
 * but it also makes it easier to spread lots of IDs in different
 * directories on your filesystem. Example:
 * $part1 = substr($alpha_id,0,1);
 * $part2 = substr($alpha_id,1,1);
 * $part3 = substr($alpha_id,2,strlen($alpha_id));
 * $destindir = "/".$part1."/".$part2."/".$part3;
 * // by reversing, directories are more evenly spread out. The
 * // first 26 directories already occupy 26 main levels
 *
 * more info on limitation:
 * - http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/165372
 *
 * if you really need this for bigger numbers you probably have to look
 * at things like: http://theserverpages.com/php/manual/en/ref.bc.php
 * or: http://theserverpages.com/php/manual/en/ref.gmp.php
 * but I haven't really dugg into this. If you have more info on those
 * matters feel free to leave a comment.
 *
 * @author  Kevin van Zonneveld <[email protected]>
 * @author  Simon Franz
 * @author  Deadfish
 * @copyright 2008 Kevin van Zonneveld (http://kevin.vanzonneveld.net)
 * @license   http://www.opensource.org/licenses/bsd-license.php New BSD Licence
 * @version   SVN: Release: $Id: alphaID.inc.php 344 2009-06-10 17:43:59Z kevin $
 * @link    http://kevin.vanzonneveld.net/
 *
 * @param mixed   $in    String or long input to translate
 * @param boolean $to_num  Reverses translation when true
 * @param mixed   $pad_up  Number or boolean padds the result up to a specified length
 * @param string  $passKey Supplying a password makes it harder to calculate the original ID
 *
 * @return mixed string or long
 */
function alphaID($in, $to_num = false, $pad_up = false, $passKey = null)
{
  $index = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
  if ($passKey !== null) {
    // Although this function's purpose is to just make the
    // ID short - and not so much secure,
    // with this patch by Simon Franz (http://blog.snaky.org/)
    // you can optionally supply a password to make it harder
    // to calculate the corresponding numeric ID
  
    for ($n = 0; $n<strlen($index); $n++) {
      $i&#91;&#93; = substr( $index,$n ,1);
    }
  
    $passhash = hash('sha256',$passKey);
    $passhash = (strlen($passhash) < strlen($index))
      ? hash('sha512',$passKey)
      : $passhash;
  
    for ($n=0; $n < strlen($index); $n++) {
      $p&#91;&#93; =  substr($passhash, $n ,1);
    }
  
    array_multisort($p,  SORT_DESC, $i);
    $index = implode($i);
  }
  
  $base  = strlen($index);
  
  if ($to_num) {
    // Digital number  <<--  alphabet letter code
    $in  = strrev($in);
    $out = 0;
    $len = strlen($in) - 1;
    for ($t = 0; $t <= $len; $t++) {
      $bcpow = bcpow($base, $len - $t);
      $out   = $out + strpos($index, substr($in, $t, 1)) * $bcpow;
    }
  
    if (is_numeric($pad_up)) {
      $pad_up--;
      if ($pad_up > 0) {
        $out -= pow($base, $pad_up);
      }
    }
    $out = sprintf('%F', $out);
    $out = substr($out, 0, strpos($out, '.'));
  } else {
    // Digital number  -->>  alphabet letter code
    if (is_numeric($pad_up)) {
      $pad_up--;
      if ($pad_up > 0) {
        $in += pow($base, $pad_up);
      }
    }
  
    $out = "";
    for ($t = floor(log($in, $base)); $t >= 0; $t--) {
      $bcp = bcpow($base, $t);
      $a   = floor($in / $bcp) % $base;
      $out = $out . substr($index, $a, 1);
      $in  = $in - ($a * $bcp);
    }
    $out = strrev($out); // reverse
  }
  
  return $out;
}

写于 2018年03月25日,由 archive.org 找回