我正在尝试根据任意用户提供的数据为每月和每日的 cronjob 构建一个随机但可预测的 cronjob 计划。每日和每月的 cronjobs 应在不同的时间运行。
目标是,如果用户重复提供相同的输入,则 cronjobs 将始终在完全相同的时间运行。哪些数据产生什么样的时间表应该“感觉”是随机的,但不一定是密码随机的。但是,如果用户只是稍微更改输入,则 cronjobs 应该在完全不同(即不相似)的时间运行。如果用户随后将输入更改回原始时间,则 cronjobs 应在与之前完全相同的时间运行。
无法持久存储生成的 cronjob 计划,即它必须严格根据用户数据构建。用户数据是任意的,即我们不能期望它具有特定的形式或长度 - 它可以是空字符串,也可以是具有 1 GB 随机数据的字符串。
应该使用仅安装 Busybox 工具的 Bash 构建 cronjob 计划。
我的想法如下:
每月 cronjob 的时间表可以用代表该月第 n 分钟的整数来表示。因此,n=123 代表该月的第 123 分钟,或该月第一天的 2:03(cronjob 时间表
3 2 1 * *
)。相比之下,n=12345 表示该月的第 12,345 分钟,或该月的第 9 天的 13:45(cronjob 时间表45 13 9 * *
)。由于 cronjob 不会在 2 月份运行,因此我们最多接受第 28 天的 23:59。因此我们需要一个 0 到 40319 之间的整数(= 28 天 * 24 小时 * 60 分钟 - 1)。
为此,我们可以创建一个
__crontab_monthly()
函数,接受任意整数。由于输入的整数不一定在预期范围内,因此我们首先对 40320 进行模运算。然后进行模运算和除法运算,得到各自的日、小时和分钟。最后我们连接 cronjob 时间表。
同样的原理也适用于日常 cronjob,只是限制为 0 到 1439 之间的整数(= 24 小时 * 60 分钟 - 1)。我们可以为此创建一个类似的
__crontab_daily()
函数。
我还不知道如何确保每月和每日的 cronjobs 在不同的时间运行 - 除了通过一些神奇的值来抵消该值......有什么想法吗?
但是,要实现此目的,我们首先需要从用户数据中计算一个随机但持久的整数,以便将它们输入到我们的
__crontab_{monthly,daily}()
函数中。由于我们必须接受任意用户数据,所以我的想法是首先计算用户数据的 md5 哈希值。这确保了结果被认为是随机的(输入中的微小变化会产生完全不同的结果),但它是可预测的,并且对于相同的数据会产生一致的结果。
md5 哈希可能是一个很好的起点,因为 md5 哈希只是 128 位数字的十六进制字符串表示形式。但是,我们无法在 Bash 中使用 128 位数字进行数学运算。因此,我产生了将两个唯一的 128 位哈希值统一合并为同一个 64 位整数的想法。我的方法是首先将哈希拆分为两个 64 位切片,执行 2^32 的模运算将每个切片压缩为 32 位,然后连接两个二进制数。这应该会产生一个 64 位有符号整数,然后可以将其输入到
__crontab_{monthly,daily}()
函数中。
到目前为止我想出了以下解决方案。用户输入存储在
$USER_DATA
变量中。我相当确定 __crontab_{monthly,daily}()
函数做了它们应该做的事情,但是 __crontab_reference()
函数更棘手......我只是不确定计算是否正确,二进制不是“我的事” ”。有人可以帮忙吗?
__crontab_reference() {
local HASH="$(md5sum <<< "$1" | cut -d ' ' -f 1)"
echo $(( 0x$(printf '%x\n' $(( 0x${HASH:0:16} % 2147483648 )))$(printf '%x\n' $(( 0x${HASH:16:16} % 2147483648 ))) ))
}
__crontab_daily() {
local NUMBER=$(( "$1" % 1440 ))
NUMBER=$(( NUMBER * ((NUMBER>0) - (NUMBER<0)) ))
local HOUR=$(( NUMBER / 60 ))
local MINUTE=$(( NUMBER % 60 ))
echo "$MINUTE $HOUR * * *"
}
__crontab_monthly() {
local NUMBER=$(( "$1" % 40320 ))
NUMBER=$(( NUMBER * ((NUMBER>0) - (NUMBER<0)) ))
local DAY=$(( NUMBER / 1440 + 1 ))
local HOUR=$(( NUMBER % 1440 / 60 ))
local MINUTE=$(( NUMBER % 1440 % 60 ))
echo "$MINUTE $HOUR $DAY * *"
}
CRONTAB_REFERENCE="$(__crontab_reference "$USER_DATA")"
echo "Daily cronjob schedule: $(__crontab_daily "$CRONTAB_REFERENCE")"
echo "Monthly cronjob schedule: $(__crontab_monthly "$CRONTAB_REFERENCE")"
对于
USER_DATA=""
,得出:
Daily cronjob schedule: 36 1 * * *
Monthly cronjob schedule: 36 1 20 * *
最后但并非最不重要的一点是,有人对替代方法有想法(请包括 PoC 代码)吗?
bc
可以直接对十六进制进行操作。
使用评论中pmf的想法:
hash=$(md5sum <<<"$data")
read md mh mm dh dm <<<$( echo $(
echo "
ibase=16; s=${hash^^}0; ibase=A
k=2^8
md= s%28+1
s/=k; mh= s%24
s/=k; mm= s%60
s/=k; dh= (mh+(s%23)+1)%24
s/=k; dm= s%60
md;mh;mm; dh;dm
" | bc
))
为了确保 mh != dh,我们将其偏移一个非零量。
bc
想要大写的十六进制。如果所使用的 shell 没有 tr
参数扩展,则可以使用 ${var^^}
进行转换。